mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-27 21:16:48 +00:00
[refactor] Scan improvements (#577)
* Handle error on unexpected barcode response * Add ManufacturerPart detail view * Support barcode scanning for manufacturer part * Refactoring for null checks * Ignore selected errors in sentry * Fix API implementation for ManufacturerPart * Update release notes * More error handling * Decode quantity betterer * Refactoring * Add option to confirm checkin details * Improve response handlign * Cleanup * Remove unused imports * Fix async function * Fix for assigning custom barcode * Handle barcode scan result for company * Fix * Adjust scan priority * Refactoring MODEL_TYPE - Use instead of duplicated const strings * @override fix
This commit is contained in:
parent
6b179d108c
commit
524c5469f1
@ -1,12 +1,15 @@
|
|||||||
### 0.17.1 - December 2024
|
### 0.17.1 - December 2024
|
||||||
---
|
---
|
||||||
|
|
||||||
|
- Add support for ManufacturerPart model
|
||||||
|
- Support barcode scanning for ManufacturerPart
|
||||||
- Fixes barcode scanning bug which prevents scanning of DataMatrix codes
|
- Fixes barcode scanning bug which prevents scanning of DataMatrix codes
|
||||||
- Display "destination" information in PurchaseOrder detail view
|
- Display "destination" information in PurchaseOrder detail view
|
||||||
- Pre-fill "location" field when receiving items against PurchaseOrder
|
- Pre-fill "location" field when receiving items against PurchaseOrder
|
||||||
- Fix display of part name in PurchaseOrderLineItem list
|
- Fix display of part name in PurchaseOrderLineItem list
|
||||||
- Adds "assigned to me" filter for Purchase Order list
|
- Adds "assigned to me" filter for Purchase Order list
|
||||||
- Adds "assigned to me" filter for Sales Order list
|
- Adds "assigned to me" filter for Sales Order list
|
||||||
|
- Updated translations
|
||||||
|
|
||||||
### 0.17.0 - December 2024
|
### 0.17.0 - December 2024
|
||||||
---
|
---
|
||||||
|
@ -11,15 +11,17 @@ import "package:inventree/api.dart";
|
|||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/barcode/barcode.dart";
|
import "package:inventree/barcode/barcode.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/inventree/sales_order.dart";
|
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
import "package:inventree/inventree/company.dart";
|
import "package:inventree/inventree/company.dart";
|
||||||
import "package:inventree/inventree/part.dart";
|
import "package:inventree/inventree/part.dart";
|
||||||
import "package:inventree/inventree/project_code.dart";
|
import "package:inventree/inventree/project_code.dart";
|
||||||
import "package:inventree/inventree/sentry.dart";
|
import "package:inventree/inventree/purchase_order.dart";
|
||||||
|
import "package:inventree/inventree/sales_order.dart";
|
||||||
import "package:inventree/inventree/stock.dart";
|
import "package:inventree/inventree/stock.dart";
|
||||||
|
|
||||||
|
import "package:inventree/inventree/sentry.dart";
|
||||||
|
|
||||||
import "package:inventree/widget/dialogs.dart";
|
import "package:inventree/widget/dialogs.dart";
|
||||||
import "package:inventree/widget/fields.dart";
|
import "package:inventree/widget/fields.dart";
|
||||||
import "package:inventree/widget/progress.dart";
|
import "package:inventree/widget/progress.dart";
|
||||||
@ -562,11 +564,17 @@ class APIFormField {
|
|||||||
Map<String, dynamic> data = item as Map<String, dynamic>;
|
Map<String, dynamic> data = item as Map<String, dynamic>;
|
||||||
|
|
||||||
switch (model) {
|
switch (model) {
|
||||||
case "part":
|
case InvenTreePart.MODEL_TYPE:
|
||||||
return InvenTreePart.fromJson(data).fullname;
|
return InvenTreePart.fromJson(data).fullname;
|
||||||
case "partcategory":
|
case InvenTreeCompany.MODEL_TYPE:
|
||||||
|
return InvenTreeCompany.fromJson(data).name;
|
||||||
|
case InvenTreePurchaseOrder.MODEL_TYPE:
|
||||||
|
return InvenTreePurchaseOrder.fromJson(data).reference;
|
||||||
|
case InvenTreeSalesOrder.MODEL_TYPE:
|
||||||
|
return InvenTreeSalesOrder.fromJson(data).reference;
|
||||||
|
case InvenTreePartCategory.MODEL_TYPE:
|
||||||
return InvenTreePartCategory.fromJson(data).pathstring;
|
return InvenTreePartCategory.fromJson(data).pathstring;
|
||||||
case "stocklocation":
|
case InvenTreeStockLocation.MODEL_TYPE:
|
||||||
return InvenTreeStockLocation.fromJson(data).pathstring;
|
return InvenTreeStockLocation.fromJson(data).pathstring;
|
||||||
default:
|
default:
|
||||||
return "itemAsString not implemented for '${model}'";
|
return "itemAsString not implemented for '${model}'";
|
||||||
@ -606,10 +614,12 @@ class APIFormField {
|
|||||||
Map<String, String> _relatedFieldFilters() {
|
Map<String, String> _relatedFieldFilters() {
|
||||||
|
|
||||||
switch (model) {
|
switch (model) {
|
||||||
case "supplierpart":
|
case InvenTreeSupplierPart.MODEL_TYPE:
|
||||||
return InvenTreeSupplierPart().defaultListFilters();
|
return InvenTreeSupplierPart().defaultListFilters();
|
||||||
case "stockitem":
|
case InvenTreeStockItem.MODEL_TYPE:
|
||||||
return InvenTreeStockItem().defaultListFilters();
|
return InvenTreeStockItem().defaultListFilters();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@ -643,7 +653,7 @@ class APIFormField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (model) {
|
switch (model) {
|
||||||
case "part":
|
case InvenTreePart.MODEL_TYPE:
|
||||||
var part = InvenTreePart.fromJson(data);
|
var part = InvenTreePart.fromJson(data);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
@ -657,14 +667,14 @@ class APIFormField {
|
|||||||
) : null,
|
) : null,
|
||||||
leading: extended ? InvenTreeAPI().getThumbnail(part.thumbnail) : null,
|
leading: extended ? InvenTreeAPI().getThumbnail(part.thumbnail) : null,
|
||||||
);
|
);
|
||||||
case "parttesttemplate":
|
case InvenTreePartTestTemplate.MODEL_TYPE:
|
||||||
var template = InvenTreePartTestTemplate.fromJson(data);
|
var template = InvenTreePartTestTemplate.fromJson(data);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(template.testName),
|
title: Text(template.testName),
|
||||||
subtitle: Text(template.description),
|
subtitle: Text(template.description),
|
||||||
);
|
);
|
||||||
case "supplierpart":
|
case InvenTreeSupplierPart.MODEL_TYPE:
|
||||||
var part = InvenTreeSupplierPart.fromJson(data);
|
var part = InvenTreeSupplierPart.fromJson(data);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
@ -673,7 +683,7 @@ class APIFormField {
|
|||||||
leading: extended ? InvenTreeAPI().getThumbnail(part.partImage) : null,
|
leading: extended ? InvenTreeAPI().getThumbnail(part.partImage) : null,
|
||||||
trailing: extended && part.supplierImage.isNotEmpty ? InvenTreeAPI().getThumbnail(part.supplierImage) : null,
|
trailing: extended && part.supplierImage.isNotEmpty ? InvenTreeAPI().getThumbnail(part.supplierImage) : null,
|
||||||
);
|
);
|
||||||
case "partcategory":
|
case InvenTreePartCategory.MODEL_TYPE:
|
||||||
|
|
||||||
var cat = InvenTreePartCategory.fromJson(data);
|
var cat = InvenTreePartCategory.fromJson(data);
|
||||||
|
|
||||||
@ -687,7 +697,7 @@ class APIFormField {
|
|||||||
style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal),
|
style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal),
|
||||||
) : null,
|
) : null,
|
||||||
);
|
);
|
||||||
case "stockitem":
|
case InvenTreeStockItem.MODEL_TYPE:
|
||||||
var item = InvenTreeStockItem.fromJson(data);
|
var item = InvenTreeStockItem.fromJson(data);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
@ -697,8 +707,7 @@ class APIFormField {
|
|||||||
leading: InvenTreeAPI().getThumbnail(item.partThumbnail),
|
leading: InvenTreeAPI().getThumbnail(item.partThumbnail),
|
||||||
trailing: Text(item.quantityString()),
|
trailing: Text(item.quantityString()),
|
||||||
);
|
);
|
||||||
case "stocklocation":
|
case InvenTreeStockLocation.MODEL_TYPE:
|
||||||
|
|
||||||
var loc = InvenTreeStockLocation.fromJson(data);
|
var loc = InvenTreeStockLocation.fromJson(data);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
@ -711,7 +720,7 @@ class APIFormField {
|
|||||||
style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal),
|
style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal),
|
||||||
) : null,
|
) : null,
|
||||||
);
|
);
|
||||||
case "salesordershipment":
|
case InvenTreeSalesOrderShipment.MODEL_TYPE:
|
||||||
var shipment = InvenTreeSalesOrderShipment.fromJson(data);
|
var shipment = InvenTreeSalesOrderShipment.fromJson(data);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
@ -733,14 +742,14 @@ class APIFormField {
|
|||||||
title: Text(name),
|
title: Text(name),
|
||||||
subtitle: Text(role),
|
subtitle: Text(role),
|
||||||
);
|
);
|
||||||
case "company":
|
case InvenTreeCompany.MODEL_TYPE:
|
||||||
var company = InvenTreeCompany.fromJson(data);
|
var company = InvenTreeCompany.fromJson(data);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(company.name),
|
title: Text(company.name),
|
||||||
subtitle: extended ? Text(company.description) : null,
|
subtitle: extended ? Text(company.description) : null,
|
||||||
leading: InvenTreeAPI().getThumbnail(company.thumbnail)
|
leading: InvenTreeAPI().getThumbnail(company.thumbnail)
|
||||||
);
|
);
|
||||||
case "projectcode":
|
case InvenTreeProjectCode.MODEL_TYPE:
|
||||||
var project_code = InvenTreeProjectCode.fromJson(data);
|
var project_code = InvenTreeProjectCode.fromJson(data);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(project_code.code),
|
title: Text(project_code.code),
|
||||||
|
@ -4,7 +4,9 @@ import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
|||||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/inventree/sales_order.dart";
|
import "package:inventree/inventree/sales_order.dart";
|
||||||
|
import "package:inventree/inventree/sentry.dart";
|
||||||
import "package:inventree/preferences.dart";
|
import "package:inventree/preferences.dart";
|
||||||
|
import "package:inventree/widget/company/manufacturer_part_detail.dart";
|
||||||
import "package:inventree/widget/order/sales_order_detail.dart";
|
import "package:inventree/widget/order/sales_order_detail.dart";
|
||||||
import "package:one_context/one_context.dart";
|
import "package:one_context/one_context.dart";
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ import "package:inventree/widget/order/purchase_order_detail.dart";
|
|||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
import "package:inventree/widget/stock/stock_detail.dart";
|
import "package:inventree/widget/stock/stock_detail.dart";
|
||||||
|
import "package:inventree/widget/company/company_detail.dart";
|
||||||
import "package:inventree/widget/company/supplier_part_detail.dart";
|
import "package:inventree/widget/company/supplier_part_detail.dart";
|
||||||
|
|
||||||
|
|
||||||
@ -176,15 +179,37 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
*/
|
*/
|
||||||
Future<void> handleSupplierPart(int pk) async {
|
Future<void> handleSupplierPart(int pk) async {
|
||||||
|
|
||||||
var supplierpart = await InvenTreeSupplierPart().get(pk);
|
var supplierPart = await InvenTreeSupplierPart().get(pk);
|
||||||
|
|
||||||
if (supplierpart is InvenTreeSupplierPart) {
|
if (supplierPart is InvenTreeSupplierPart) {
|
||||||
OneContext().pop();
|
OneContext().pop();
|
||||||
OneContext().push(MaterialPageRoute(
|
OneContext().push(MaterialPageRoute(
|
||||||
builder: (context) => SupplierPartDetailWidget(supplierpart)));
|
builder: (context) => SupplierPartDetailWidget(supplierPart)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Response when a "ManufacturerPart" instance is scanned
|
||||||
|
*/
|
||||||
|
Future<void> handleManufacturerPart(int pk) async {
|
||||||
|
var manufacturerPart = await InvenTreeManufacturerPart().get(pk);
|
||||||
|
|
||||||
|
if (manufacturerPart is InvenTreeManufacturerPart) {
|
||||||
|
OneContext().pop();
|
||||||
|
OneContext().push(MaterialPageRoute(
|
||||||
|
builder: (context) => ManufacturerPartDetailWidget(manufacturerPart)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleCompany(int pk) async {
|
||||||
|
var company = await InvenTreeCompany().get(pk);
|
||||||
|
|
||||||
|
if (company is InvenTreeCompany) {
|
||||||
|
OneContext().pop();
|
||||||
|
OneContext().push(MaterialPageRoute(
|
||||||
|
builder: (context) => CompanyDetailWidget(company)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Response when a "PurchaseOrder" instance is scanned
|
* Response when a "PurchaseOrder" instance is scanned
|
||||||
@ -218,26 +243,32 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
// The following model types can be matched with barcodes
|
// The following model types can be matched with barcodes
|
||||||
List<String> validModels = [
|
List<String> validModels = [
|
||||||
"part",
|
InvenTreePart.MODEL_TYPE,
|
||||||
"stockitem",
|
InvenTreeCompany.MODEL_TYPE,
|
||||||
"stocklocation",
|
InvenTreeStockItem.MODEL_TYPE,
|
||||||
"supplierpart",
|
InvenTreeStockLocation.MODEL_TYPE,
|
||||||
|
InvenTreeSupplierPart.MODEL_TYPE,
|
||||||
|
InvenTreeManufacturerPart.MODEL_TYPE,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
if (InvenTreeAPI().supportsOrderBarcodes) {
|
if (InvenTreeAPI().supportsOrderBarcodes) {
|
||||||
validModels.add("purchaseorder");
|
validModels.add(InvenTreePurchaseOrder.MODEL_TYPE);
|
||||||
validModels.add("salesorder");
|
validModels.add(InvenTreeSalesOrder.MODEL_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var key in validModels) {
|
for (var key in validModels) {
|
||||||
if (data.containsKey(key)) {
|
if (data.containsKey(key)) {
|
||||||
pk = (data[key]?["pk"] ?? -1) as int;
|
try {
|
||||||
|
pk = (data[key]?["pk"] ?? -1) as int;
|
||||||
|
|
||||||
// Break on the first valid match found
|
// Break on the first valid match found
|
||||||
if (pk > 0) {
|
if (pk > 0) {
|
||||||
model = key;
|
model = key;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
sentryReportError("onBarcodeMatched", error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -248,25 +279,30 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
barcodeSuccessTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
switch (model) {
|
switch (model) {
|
||||||
case "part":
|
case InvenTreeStockItem.MODEL_TYPE:
|
||||||
await handlePart(pk);
|
|
||||||
return;
|
|
||||||
case "stockitem":
|
|
||||||
await handleStockItem(pk);
|
await handleStockItem(pk);
|
||||||
return;
|
return;
|
||||||
case "stocklocation":
|
case InvenTreePurchaseOrder.MODEL_TYPE:
|
||||||
await handleStockLocation(pk);
|
|
||||||
return;
|
|
||||||
case "supplierpart":
|
|
||||||
await handleSupplierPart(pk);
|
|
||||||
return;
|
|
||||||
case "purchaseorder":
|
|
||||||
await handlePurchaseOrder(pk);
|
await handlePurchaseOrder(pk);
|
||||||
return;
|
return;
|
||||||
case "salesorder":
|
case InvenTreeSalesOrder.MODEL_TYPE:
|
||||||
await handleSalesOrder(pk);
|
await handleSalesOrder(pk);
|
||||||
return;
|
return;
|
||||||
// TODO: Handle manufacturer part
|
case InvenTreeStockLocation.MODEL_TYPE:
|
||||||
|
await handleStockLocation(pk);
|
||||||
|
return;
|
||||||
|
case InvenTreeSupplierPart.MODEL_TYPE:
|
||||||
|
await handleSupplierPart(pk);
|
||||||
|
return;
|
||||||
|
case InvenTreeManufacturerPart.MODEL_TYPE:
|
||||||
|
await handleManufacturerPart(pk);
|
||||||
|
return;
|
||||||
|
case InvenTreePart.MODEL_TYPE:
|
||||||
|
await handlePart(pk);
|
||||||
|
return;
|
||||||
|
case InvenTreeCompany.MODEL_TYPE:
|
||||||
|
await handleCompany(pk);
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
// Fall through to failure state
|
// Fall through to failure state
|
||||||
break;
|
break;
|
||||||
@ -324,21 +360,6 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||||
|
|
||||||
barcodeFailureTone();
|
|
||||||
|
|
||||||
// If the barcode is known, we can"t assign it to the stock item!
|
|
||||||
showSnackIcon(
|
|
||||||
L10().barcodeInUse,
|
|
||||||
icon: Icons.qr_code,
|
|
||||||
success: false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
|
|
||||||
// If the barcode is unknown, we *can* assign it to the stock item!
|
|
||||||
|
|
||||||
if (!data.containsKey("hash") && !data.containsKey("barcode_hash")) {
|
if (!data.containsKey("hash") && !data.containsKey("barcode_hash")) {
|
||||||
showServerError(
|
showServerError(
|
||||||
"barcode/",
|
"barcode/",
|
||||||
@ -370,6 +391,12 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
|
||||||
|
await onBarcodeMatched(data);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
|||||||
/*
|
/*
|
||||||
* Callback function when a barcode is scanned
|
* Callback function when a barcode is scanned
|
||||||
*/
|
*/
|
||||||
void _onScanSuccess(Code? code) {
|
Future<void> onScanSuccess(Code? code) async {
|
||||||
|
|
||||||
if (scanning_paused) {
|
if (scanning_paused) {
|
||||||
return;
|
return;
|
||||||
@ -122,18 +122,16 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
scanned_code = barcode;
|
scanned_code = barcode;
|
||||||
scanning_paused = barcode.isNotEmpty;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (barcode.isNotEmpty) {
|
if (barcode.isNotEmpty) {
|
||||||
|
|
||||||
handleBarcodeData(barcode).then((_) {
|
pauseScan();
|
||||||
|
|
||||||
|
await handleBarcodeData(barcode).then((_) {
|
||||||
if (!single_scanning && mounted) {
|
if (!single_scanning && mounted) {
|
||||||
// Resume next scan
|
resumeScan();
|
||||||
setState(() {
|
|
||||||
scanning_paused = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -186,7 +184,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
|||||||
Widget BarcodeReader(BuildContext context) {
|
Widget BarcodeReader(BuildContext context) {
|
||||||
|
|
||||||
return ReaderWidget(
|
return ReaderWidget(
|
||||||
onScan: _onScanSuccess,
|
onScan: onScanSuccess,
|
||||||
isMultiScan: false,
|
isMultiScan: false,
|
||||||
tryHarder: true,
|
tryHarder: true,
|
||||||
tryInverted: true,
|
tryInverted: true,
|
||||||
|
@ -79,14 +79,27 @@ class BarcodeHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = await InvenTreeAPI().post(
|
APIResponse? response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await InvenTreeAPI().post(
|
||||||
url,
|
url,
|
||||||
body: {
|
body: {
|
||||||
"barcode": barcode,
|
"barcode": barcode,
|
||||||
...extra_data,
|
...extra_data,
|
||||||
},
|
},
|
||||||
expectedStatusCode: null, // Do not show an error on "unexpected code"
|
expectedStatusCode: null, // Do not show an error on "unexpected code"
|
||||||
);
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
sentryReportError("Barcode.processBarcode", error, stackTrace);
|
||||||
|
response = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
barcodeFailureTone();
|
||||||
|
showSnackIcon(L10().barcodeError, success: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
debug("Barcode scan response" + response.data.toString());
|
debug("Barcode scan response" + response.data.toString());
|
||||||
|
|
||||||
@ -94,7 +107,7 @@ class BarcodeHandler {
|
|||||||
|
|
||||||
// Handle strange response from the server
|
// Handle strange response from the server
|
||||||
if (!response.isValid() || !response.isMap()) {
|
if (!response.isValid() || !response.isMap()) {
|
||||||
onBarcodeUnknown({});
|
await onBarcodeUnknown({});
|
||||||
|
|
||||||
showSnackIcon(L10().serverError, success: false);
|
showSnackIcon(L10().serverError, success: false);
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
import "package:inventree/preferences.dart";
|
||||||
import "package:one_context/one_context.dart";
|
import "package:one_context/one_context.dart";
|
||||||
|
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
import "package:inventree/api_form.dart";
|
|
||||||
|
|
||||||
import "package:inventree/barcode/barcode.dart";
|
import "package:inventree/barcode/barcode.dart";
|
||||||
import "package:inventree/barcode/handler.dart";
|
import "package:inventree/barcode/handler.dart";
|
||||||
@ -23,10 +21,11 @@ import "package:inventree/widget/snacks.dart";
|
|||||||
*/
|
*/
|
||||||
class POReceiveBarcodeHandler extends BarcodeHandler {
|
class POReceiveBarcodeHandler extends BarcodeHandler {
|
||||||
|
|
||||||
POReceiveBarcodeHandler({this.purchaseOrder, this.location});
|
POReceiveBarcodeHandler({this.purchaseOrder, this.location, this.lineItem});
|
||||||
|
|
||||||
InvenTreePurchaseOrder? purchaseOrder;
|
InvenTreePurchaseOrder? purchaseOrder;
|
||||||
InvenTreeStockLocation? location;
|
InvenTreeStockLocation? location;
|
||||||
|
InvenTreePOLineItem? lineItem;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getOverlayText(BuildContext context) => L10().barcodeReceivePart;
|
String getOverlayText(BuildContext context) => L10().barcodeReceivePart;
|
||||||
@ -34,11 +33,15 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
|
|||||||
@override
|
@override
|
||||||
Future<void> processBarcode(String barcode,
|
Future<void> processBarcode(String barcode,
|
||||||
{String url = "barcode/po-receive/",
|
{String url = "barcode/po-receive/",
|
||||||
Map<String, dynamic> extra_data = const {}}) {
|
Map<String, dynamic> extra_data = const {}}) async {
|
||||||
|
|
||||||
|
final bool confirm = await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true);
|
||||||
|
|
||||||
final po_extra_data = {
|
final po_extra_data = {
|
||||||
"purchase_order": purchaseOrder?.pk,
|
"purchase_order": purchaseOrder?.pk,
|
||||||
"location": location?.pk,
|
"location": location?.pk,
|
||||||
|
"line_item": lineItem?.pk,
|
||||||
|
"auto_allocate": !confirm,
|
||||||
...extra_data,
|
...extra_data,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,11 +50,13 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||||
if (!data.containsKey("lineitem")) {
|
|
||||||
|
if (data.containsKey("lineitem") || data.containsKey("success")) {
|
||||||
|
barcodeSuccess(L10().receivedItem);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
return onBarcodeUnknown(data);
|
return onBarcodeUnknown(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
barcodeSuccess(L10().receivedItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -66,49 +71,41 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
|
|||||||
showSnackIcon(L10().missingData, success: false);
|
showSnackIcon(L10().missingData, success: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct fields to receive
|
// At minimum, we need the line item ID value
|
||||||
Map<String, dynamic> fields = {
|
final int? lineItemId = lineItemData["pk"] as int?;
|
||||||
"line_item": {
|
|
||||||
"parent": "items",
|
if (lineItemId == null) {
|
||||||
"nested": true,
|
barcodeFailureTone();
|
||||||
"hidden": true,
|
return;
|
||||||
"value": lineItemData["pk"] as int,
|
}
|
||||||
},
|
|
||||||
"quantity": {
|
InvenTreePOLineItem? lineItem = await InvenTreePOLineItem().get(lineItemId) as InvenTreePOLineItem?;
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
if (lineItem == null) {
|
||||||
"value": lineItemData["quantity"] as double?,
|
barcodeFailureTone();
|
||||||
},
|
return;
|
||||||
"status": {
|
}
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
// Next, extract the "optional" fields
|
||||||
},
|
|
||||||
"location": {
|
// Extract information from the returned server response
|
||||||
"value": lineItemData["location"] as int?,
|
double? quantity = double.tryParse((lineItemData["quantity"] ?? "0").toString());
|
||||||
},
|
int? destination = lineItemData["location"] as int?;
|
||||||
"barcode": {
|
String? barcode = data["barcode_data"] as String?;
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
// Discard the barcode scanner at this stage
|
||||||
"hidden": true,
|
if (OneContext.hasContext) {
|
||||||
"type": "barcode",
|
OneContext().pop();
|
||||||
"value": data["barcode_data"] as String,
|
}
|
||||||
|
|
||||||
|
await lineItem.receive(
|
||||||
|
OneContext().context!,
|
||||||
|
destination: destination,
|
||||||
|
quantity: quantity,
|
||||||
|
barcode: barcode,
|
||||||
|
onSuccess: () {
|
||||||
|
showSnackIcon(L10().receivedItem, success: true);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
final context = OneContext().context!;
|
|
||||||
final purchase_order_pk = lineItemData["purchase_order"];
|
|
||||||
final receive_url = "${InvenTreePurchaseOrder().URL}${purchase_order_pk}/receive/";
|
|
||||||
|
|
||||||
launchApiForm(
|
|
||||||
context,
|
|
||||||
L10().receiveItem,
|
|
||||||
receive_url,
|
|
||||||
fields,
|
|
||||||
method: "POST",
|
|
||||||
icon: TablerIcons.transition_right,
|
|
||||||
onSuccess: (data) async {
|
|
||||||
showSnackIcon(L10().receivedItem, success: true);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class BarcodeScanStockLocationHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
// We expect that the barcode points to a 'stocklocation'
|
// We expect that the barcode points to a 'stocklocation'
|
||||||
if (data.containsKey("stocklocation")) {
|
if (data.containsKey("stocklocation")) {
|
||||||
int _loc = (data["stocklocation"]["pk"] ?? -1) as int;
|
int _loc = (data["stocklocation"]?["pk"] ?? -1) as int;
|
||||||
|
|
||||||
// A valid stock location!
|
// A valid stock location!
|
||||||
if (_loc > 0) {
|
if (_loc > 0) {
|
||||||
@ -83,7 +83,7 @@ class BarcodeScanStockItemHandler extends BarcodeHandler {
|
|||||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||||
// We expect that the barcode points to a 'stockitem'
|
// We expect that the barcode points to a 'stockitem'
|
||||||
if (data.containsKey("stockitem")) {
|
if (data.containsKey("stockitem")) {
|
||||||
int _item = (data["stockitem"]["pk"] ?? -1) as int;
|
int _item = (data["stockitem"]?["pk"] ?? -1) as int;
|
||||||
|
|
||||||
// A valid stock location!
|
// A valid stock location!
|
||||||
if (_item > 0) {
|
if (_item > 0) {
|
||||||
|
@ -18,6 +18,8 @@ class InvenTreeCompany extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "company/";
|
String get URL => "company/";
|
||||||
|
|
||||||
|
static const String MODEL_TYPE = "company";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"];
|
List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"];
|
||||||
|
|
||||||
@ -128,6 +130,8 @@ class InvenTreeSupplierPart extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "company/part/";
|
String get URL => "company/part/";
|
||||||
|
|
||||||
|
static const String MODEL_TYPE = "supplierpart";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["part", "purchase_order"];
|
List<String> get rolesRequired => ["part", "purchase_order"];
|
||||||
|
|
||||||
@ -171,7 +175,7 @@ class InvenTreeSupplierPart extends InvenTreeModel {
|
|||||||
|
|
||||||
String get MPN => getString("MPN", subKey: "manufacturer_part_detail");
|
String get MPN => getString("MPN", subKey: "manufacturer_part_detail");
|
||||||
|
|
||||||
String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
||||||
|
|
||||||
int get manufacturerPartId => getInt("manufacturer_part");
|
int get manufacturerPartId => getInt("manufacturer_part");
|
||||||
|
|
||||||
@ -179,14 +183,14 @@ class InvenTreeSupplierPart extends InvenTreeModel {
|
|||||||
|
|
||||||
String get supplierName => getString("name", subKey: "supplier_detail");
|
String get supplierName => getString("name", subKey: "supplier_detail");
|
||||||
|
|
||||||
String get supplierImage => (jsondata["supplier_detail"]?["image"] ?? jsondata["supplier_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
String get supplierImage => (jsondata["supplier_detail"]?["image"] ?? jsondata["supplier_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
||||||
|
|
||||||
String get SKU => getString("SKU");
|
String get SKU => getString("SKU");
|
||||||
|
|
||||||
bool get active => getBool("active", backup: true);
|
bool get active => getBool("active", backup: true);
|
||||||
|
|
||||||
int get partId => getInt("part");
|
int get partId => getInt("part");
|
||||||
|
|
||||||
String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
||||||
|
|
||||||
String get partName => getString("name", subKey: "part_detail");
|
String get partName => getString("name", subKey: "part_detail");
|
||||||
@ -219,21 +223,52 @@ class InvenTreeManufacturerPart extends InvenTreeModel {
|
|||||||
InvenTreeManufacturerPart.fromJson(Map<String, dynamic> json) : super.fromJson(json);
|
InvenTreeManufacturerPart.fromJson(Map<String, dynamic> json) : super.fromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String url = "company/part/manufacturer/";
|
String URL = "company/part/manufacturer/";
|
||||||
|
|
||||||
|
static const String MODEL_TYPE = "manufacturerpart";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, String> defaultListFilters() {
|
List<String> get rolesRequired => ["part"];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Map<String, dynamic>> formFields() {
|
||||||
|
Map<String, Map<String, dynamic>> fields = {
|
||||||
|
"manufacturer": {},
|
||||||
|
"MPN": {},
|
||||||
|
"link": {},
|
||||||
|
};
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String> defaultFilters() {
|
||||||
return {
|
return {
|
||||||
"manufacturer_detail": "true",
|
"manufacturer_detail": "true",
|
||||||
|
"part_detail": "true",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get partId => getInt("part");
|
int get partId => getInt("part");
|
||||||
|
|
||||||
|
String get partName => getString("name", subKey: "part_detail");
|
||||||
|
|
||||||
|
String get partDescription => getString("description", subKey: "part_detail");
|
||||||
|
|
||||||
|
String get partIPN => getString("IPN", subKey: "part_detail");
|
||||||
|
|
||||||
|
String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
||||||
|
|
||||||
int get manufacturerId => getInt("manufacturer");
|
int get manufacturerId => getInt("manufacturer");
|
||||||
|
|
||||||
|
String get manufacturerName => getString("name", subKey: "manufacturer_detail");
|
||||||
|
|
||||||
|
String get manufacturerDescription => getString("description", subKey: "manufacturer_detail");
|
||||||
|
|
||||||
|
String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
|
||||||
|
|
||||||
String get MPN => getString("MPN");
|
String get MPN => getString("MPN");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeManufacturerPart.fromJson(json);
|
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeManufacturerPart.fromJson(json);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class InvenTreeModel {
|
|||||||
String get WEB_URL => "";
|
String get WEB_URL => "";
|
||||||
|
|
||||||
// Return the "model type" of this model
|
// Return the "model type" of this model
|
||||||
String get MODEL_TYPE => "";
|
static const String MODEL_TYPE = "";
|
||||||
|
|
||||||
// Helper function to set a value in the JSON data
|
// Helper function to set a value in the JSON data
|
||||||
void setValue(String key, dynamic value) {
|
void setValue(String key, dynamic value) {
|
||||||
|
@ -23,6 +23,8 @@ class InvenTreePartCategory extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "part/category/";
|
String get URL => "part/category/";
|
||||||
|
|
||||||
|
static const String MODEL_TYPE = "partcategory";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["part_category"];
|
List<String> get rolesRequired => ["part_category"];
|
||||||
|
|
||||||
@ -79,6 +81,8 @@ class InvenTreePartTestTemplate extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "part/test-template/";
|
String get URL => "part/test-template/";
|
||||||
|
|
||||||
|
static const String MODEL_TYPE = "parttesttemplate";
|
||||||
|
|
||||||
String get key => getString("key");
|
String get key => getString("key");
|
||||||
|
|
||||||
String get testName => getString("test_name");
|
String get testName => getString("test_name");
|
||||||
@ -192,8 +196,7 @@ class InvenTreePart extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "part/";
|
String get URL => "part/";
|
||||||
|
|
||||||
@override
|
static const String MODEL_TYPE = "part";
|
||||||
String get MODEL_TYPE => "part";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["part"];
|
List<String> get rolesRequired => ["part"];
|
||||||
|
@ -16,6 +16,8 @@ class InvenTreeProjectCode extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "project-code/";
|
String get URL => "project-code/";
|
||||||
|
|
||||||
|
static const String MODEL_TYPE = "projectcode";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Map<String, dynamic>> formFields() {
|
Map<String, Map<String, dynamic>> formFields() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import "package:flutter/cupertino.dart";
|
||||||
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/inventree/company.dart";
|
import "package:inventree/inventree/company.dart";
|
||||||
@ -5,6 +7,9 @@ import "package:inventree/inventree/model.dart";
|
|||||||
import "package:inventree/inventree/orders.dart";
|
import "package:inventree/inventree/orders.dart";
|
||||||
import "package:inventree/widget/progress.dart";
|
import "package:inventree/widget/progress.dart";
|
||||||
|
|
||||||
|
import "package:inventree/api_form.dart";
|
||||||
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class representing an individual PurchaseOrder instance
|
* Class representing an individual PurchaseOrder instance
|
||||||
@ -21,8 +26,7 @@ class InvenTreePurchaseOrder extends InvenTreeOrder {
|
|||||||
@override
|
@override
|
||||||
String get URL => "order/po/";
|
String get URL => "order/po/";
|
||||||
|
|
||||||
@override
|
static const String MODEL_TYPE = "purchaseorder";
|
||||||
String get MODEL_TYPE => "purchaseorder";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["purchase_order"];
|
List<String> get rolesRequired => ["purchase_order"];
|
||||||
@ -212,6 +216,16 @@ class InvenTreePOLineItem extends InvenTreeOrderLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InvenTreePurchaseOrder? get purchaseOrder {
|
||||||
|
dynamic detail = jsondata["order_detail"];
|
||||||
|
|
||||||
|
if (detail == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return InvenTreePurchaseOrder.fromJson(detail as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String get SKU => getString("SKU", subKey: "supplier_part_detail");
|
String get SKU => getString("SKU", subKey: "supplier_part_detail");
|
||||||
|
|
||||||
double get purchasePrice => getDouble("purchase_price");
|
double get purchasePrice => getDouble("purchase_price");
|
||||||
@ -223,6 +237,72 @@ class InvenTreePOLineItem extends InvenTreeOrderLine {
|
|||||||
Map<String, dynamic> get orderDetail => getMap("order_detail");
|
Map<String, dynamic> get orderDetail => getMap("order_detail");
|
||||||
|
|
||||||
Map<String, dynamic> get destinationDetail => getMap("destination_detail");
|
Map<String, dynamic> get destinationDetail => getMap("destination_detail");
|
||||||
|
|
||||||
|
// Receive this line item into stock
|
||||||
|
Future<void> receive(BuildContext context, {int? destination, double? quantity, String? barcode, Function? onSuccess}) async {
|
||||||
|
// Infer the destination location from the line item if not provided
|
||||||
|
if (destinationId > 0) {
|
||||||
|
destination = destinationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination ??= (orderDetail["destination"]) as int?;
|
||||||
|
|
||||||
|
quantity ??= outstanding;
|
||||||
|
|
||||||
|
// Construct form fields
|
||||||
|
Map<String, dynamic> fields = {
|
||||||
|
"line_item": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
"hidden": true,
|
||||||
|
"value": pk,
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
"value": quantity,
|
||||||
|
},
|
||||||
|
"location": {},
|
||||||
|
"status": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
},
|
||||||
|
"batch_code": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
},
|
||||||
|
"barcode": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
"type": "barcode",
|
||||||
|
"label": L10().barcodeAssign,
|
||||||
|
"value": barcode,
|
||||||
|
"required": false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (destination != null && destination > 0) {
|
||||||
|
fields["location"]?["value"] = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
InvenTreePurchaseOrder? order = purchaseOrder;
|
||||||
|
|
||||||
|
if (order != null) {
|
||||||
|
await launchApiForm(
|
||||||
|
context,
|
||||||
|
L10().receiveItem,
|
||||||
|
order.receive_url,
|
||||||
|
fields,
|
||||||
|
method: "POST",
|
||||||
|
icon: TablerIcons.transition_right,
|
||||||
|
onSuccess: (data) {
|
||||||
|
if (onSuccess != null) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -24,8 +24,7 @@ class InvenTreeSalesOrder extends InvenTreeOrder {
|
|||||||
@override
|
@override
|
||||||
String get URL => "order/so/";
|
String get URL => "order/so/";
|
||||||
|
|
||||||
@override
|
static const String MODEL_TYPE = "salesorder";
|
||||||
String get MODEL_TYPE => "salesorder";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["sales_order"];
|
List<String> get rolesRequired => ["sales_order"];
|
||||||
@ -250,6 +249,8 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "/order/so/shipment/";
|
String get URL => "/order/so/shipment/";
|
||||||
|
|
||||||
|
static const String MODEL_TYPE = "salesordershipment";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Map<String, dynamic>> formFields() {
|
Map<String, Map<String, dynamic>> formFields() {
|
||||||
Map<String, Map<String, dynamic>> fields = {
|
Map<String, Map<String, dynamic>> fields = {
|
||||||
|
@ -158,6 +158,11 @@ Future<bool> sentryReportMessage(String message, {Map<String, String>? context})
|
|||||||
*/
|
*/
|
||||||
Future<void> sentryReportError(String source, dynamic error, StackTrace? stackTrace, {Map<String, String> context = const {}}) async {
|
Future<void> sentryReportError(String source, dynamic error, StackTrace? stackTrace, {Map<String, String> context = const {}}) async {
|
||||||
|
|
||||||
|
if (sentryIgnoreError(error)) {
|
||||||
|
// No action on this error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
print("----- Sentry Intercepted error: $error -----");
|
print("----- Sentry Intercepted error: $error -----");
|
||||||
print(stackTrace);
|
print(stackTrace);
|
||||||
|
|
||||||
@ -228,3 +233,18 @@ Future<void> sentryReportError(String source, dynamic error, StackTrace? stackTr
|
|||||||
print("Uploaded information to Sentry.io : ${response.toString()}");
|
print("Uploaded information to Sentry.io : ${response.toString()}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test if a certain error should be ignored by Sentry
|
||||||
|
*/
|
||||||
|
bool sentryIgnoreError(dynamic error) {
|
||||||
|
// Ignore 404 errors for media files
|
||||||
|
if (error is HttpException) {
|
||||||
|
if (error.uri.toString().contains("/media/") && error.message.contains("404")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -98,7 +98,7 @@ class InvenTreeStockItemHistory extends InvenTreeModel {
|
|||||||
String get URL => "stock/track/";
|
String get URL => "stock/track/";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, String> defaultListFilters() {
|
Map<String, String> defaultFilters() {
|
||||||
|
|
||||||
// By default, order by decreasing date
|
// By default, order by decreasing date
|
||||||
return {
|
return {
|
||||||
@ -168,8 +168,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "stock/";
|
String get URL => "stock/";
|
||||||
|
|
||||||
@override
|
static const String MODEL_TYPE = "stockitem";
|
||||||
String get MODEL_TYPE => "stockitem";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["stock"];
|
List<String> get rolesRequired => ["stock"];
|
||||||
@ -206,7 +205,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
|
|
||||||
if (isSerialized()) {
|
if (isSerialized()) {
|
||||||
// Prevent editing of 'quantity' field if the item is serialized
|
// Prevent editing of 'quantity' field if the item is serialized
|
||||||
fields["quantity"]["hidden"] = true;
|
fields["quantity"]?["hidden"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old API does not support these fields
|
// Old API does not support these fields
|
||||||
@ -395,7 +394,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
|
|
||||||
// Use the detailed part information as priority
|
// Use the detailed part information as priority
|
||||||
if (jsondata.containsKey("part_detail")) {
|
if (jsondata.containsKey("part_detail")) {
|
||||||
nm = (jsondata["part_detail"]["full_name"] ?? "") as String;
|
nm = (jsondata["part_detail"]?["full_name"] ?? "") as String;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backup if first value fails
|
// Backup if first value fails
|
||||||
@ -411,7 +410,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
|
|
||||||
// Use the detailed part description as priority
|
// Use the detailed part description as priority
|
||||||
if (jsondata.containsKey("part_detail")) {
|
if (jsondata.containsKey("part_detail")) {
|
||||||
desc = (jsondata["part_detail"]["description"] ?? "") as String;
|
desc = (jsondata["part_detail"]?["description"] ?? "") as String;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desc.isEmpty) {
|
if (desc.isEmpty) {
|
||||||
@ -425,7 +424,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
String img = "";
|
String img = "";
|
||||||
|
|
||||||
if (jsondata.containsKey("part_detail")) {
|
if (jsondata.containsKey("part_detail")) {
|
||||||
img = (jsondata["part_detail"]["thumbnail"] ?? "") as String;
|
img = (jsondata["part_detail"]?["thumbnail"] ?? "") as String;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (img.isEmpty) {
|
if (img.isEmpty) {
|
||||||
@ -468,7 +467,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
if (jsondata.containsKey("supplier_part_detail")) {
|
if (jsondata.containsKey("supplier_part_detail")) {
|
||||||
thumb = (jsondata["supplier_part_detail"]?["supplier_detail"]?["image"] ?? "") as String;
|
thumb = (jsondata["supplier_part_detail"]?["supplier_detail"]?["image"] ?? "") as String;
|
||||||
} else if (jsondata.containsKey("supplier_detail")) {
|
} else if (jsondata.containsKey("supplier_detail")) {
|
||||||
thumb = (jsondata["supplier_detail"]["image"] ?? "") as String;
|
thumb = (jsondata["supplier_detail"]?["image"] ?? "") as String;
|
||||||
}
|
}
|
||||||
|
|
||||||
return thumb;
|
return thumb;
|
||||||
@ -681,8 +680,7 @@ class InvenTreeStockLocation extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "stock/location/";
|
String get URL => "stock/location/";
|
||||||
|
|
||||||
@override
|
static const String MODEL_TYPE = "stocklocation";
|
||||||
String get MODEL_TYPE => "stocklocation";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["stock_location"];
|
List<String> get rolesRequired => ["stock_location"];
|
||||||
|
@ -608,6 +608,9 @@
|
|||||||
"itemDeleted": "Item has been removed",
|
"itemDeleted": "Item has been removed",
|
||||||
"@itemDeleted": {},
|
"@itemDeleted": {},
|
||||||
|
|
||||||
|
"itemUpdated": "Item updated",
|
||||||
|
"@itemUpdated": {},
|
||||||
|
|
||||||
"keywords": "Keywords",
|
"keywords": "Keywords",
|
||||||
"@keywords": {},
|
"@keywords": {},
|
||||||
|
|
||||||
@ -684,6 +687,12 @@
|
|||||||
"lost": "Lost",
|
"lost": "Lost",
|
||||||
"@lost": {},
|
"@lost": {},
|
||||||
|
|
||||||
|
"manufacturerPart": "Manufacturer Part",
|
||||||
|
"@manufacturerPart": {},
|
||||||
|
|
||||||
|
"manufacturerPartEdit": "Edit Manufacturer Part",
|
||||||
|
"@manufacturerPartEdit": {},
|
||||||
|
|
||||||
"manufacturerPartNumber": "Manufacturer Part Number",
|
"manufacturerPartNumber": "Manufacturer Part Number",
|
||||||
"@manufacturerPartNumber": {},
|
"@manufacturerPartNumber": {},
|
||||||
|
|
||||||
@ -915,6 +924,12 @@
|
|||||||
"projectCode": "Project Code",
|
"projectCode": "Project Code",
|
||||||
"@projectCode": {},
|
"@projectCode": {},
|
||||||
|
|
||||||
|
"purchaseOrderConfirmScan": "Confirm Scan Data",
|
||||||
|
"@purchaseOrderConfirmScan": {},
|
||||||
|
|
||||||
|
"purchaseOrderConfirmScanDetail": "Confirm details when scanning in items",
|
||||||
|
"@purchaseOrderConfirmScanDetail": {},
|
||||||
|
|
||||||
"purchaseOrderEnable": "Enable Purchase Orders",
|
"purchaseOrderEnable": "Enable Purchase Orders",
|
||||||
"@purchaseOrderEnable": {},
|
"@purchaseOrderEnable": {},
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ const String INV_STOCK_CONFIRM_SCAN = "stockConfirmScan";
|
|||||||
// Purchase order settings
|
// Purchase order settings
|
||||||
const String INV_PO_ENABLE = "poEnable";
|
const String INV_PO_ENABLE = "poEnable";
|
||||||
const String INV_PO_SHOW_CAMERA = "poShowCamera";
|
const String INV_PO_SHOW_CAMERA = "poShowCamera";
|
||||||
|
const String INV_PO_CONFIRM_SCAN = "poConfirmScan";
|
||||||
|
|
||||||
// Sales order settings
|
// Sales order settings
|
||||||
const String INV_SO_ENABLE = "soEnable";
|
const String INV_SO_ENABLE = "soEnable";
|
||||||
|
@ -19,6 +19,7 @@ class _InvenTreePurchaseOrderSettingsState extends State<InvenTreePurchaseOrderS
|
|||||||
|
|
||||||
bool poEnable = true;
|
bool poEnable = true;
|
||||||
bool poShowCamera = true;
|
bool poShowCamera = true;
|
||||||
|
bool poConfirmScan = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -30,6 +31,7 @@ class _InvenTreePurchaseOrderSettingsState extends State<InvenTreePurchaseOrderS
|
|||||||
Future<void> loadSettings() async {
|
Future<void> loadSettings() async {
|
||||||
poEnable = await InvenTreeSettingsManager().getBool(INV_PO_ENABLE, true);
|
poEnable = await InvenTreeSettingsManager().getBool(INV_PO_ENABLE, true);
|
||||||
poShowCamera = await InvenTreeSettingsManager().getBool(INV_PO_SHOW_CAMERA, true);
|
poShowCamera = await InvenTreeSettingsManager().getBool(INV_PO_SHOW_CAMERA, true);
|
||||||
|
poConfirmScan = await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -75,6 +77,20 @@ class _InvenTreePurchaseOrderSettingsState extends State<InvenTreePurchaseOrderS
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().purchaseOrderConfirmScan),
|
||||||
|
subtitle: Text(L10().purchaseOrderConfirmScanDetail),
|
||||||
|
leading: Icon(TablerIcons.barcode),
|
||||||
|
trailing: Switch (
|
||||||
|
value: poConfirmScan,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
InvenTreeSettingsManager().setValue(INV_PO_CONFIRM_SCAN, value);
|
||||||
|
setState(() {
|
||||||
|
poConfirmScan = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
183
lib/widget/company/manufacturer_part_detail.dart
Normal file
183
lib/widget/company/manufacturer_part_detail.dart
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
|
|
||||||
|
import "package:inventree/l10.dart";
|
||||||
|
import "package:inventree/api.dart";
|
||||||
|
import "package:inventree/app_colors.dart";
|
||||||
|
|
||||||
|
import "package:inventree/inventree/company.dart";
|
||||||
|
import "package:inventree/inventree/part.dart";
|
||||||
|
|
||||||
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
|
import "package:inventree/widget/snacks.dart";
|
||||||
|
import "package:inventree/widget/progress.dart";
|
||||||
|
|
||||||
|
import "package:inventree/widget/part/part_detail.dart";
|
||||||
|
import "package:inventree/widget/company/company_detail.dart";
|
||||||
|
import "package:url_launcher/url_launcher.dart";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Detail widget for viewing a single ManufacturerPart instance
|
||||||
|
*/
|
||||||
|
class ManufacturerPartDetailWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
const ManufacturerPartDetailWidget(this.manufacturerPart, {Key? key})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final InvenTreeManufacturerPart manufacturerPart;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ManufacturerPartDisplayState createState() => _ManufacturerPartDisplayState();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class _ManufacturerPartDisplayState extends RefreshableState<ManufacturerPartDetailWidget> {
|
||||||
|
|
||||||
|
_ManufacturerPartDisplayState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAppBarTitle() => L10().manufacturerPart;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> request(BuildContext context) async {
|
||||||
|
final bool result = widget.manufacturerPart.pk > 0 &&
|
||||||
|
await widget.manufacturerPart.reload();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> editManufacturerPart(BuildContext context) async {
|
||||||
|
widget.manufacturerPart.editForm(
|
||||||
|
context,
|
||||||
|
L10().manufacturerPartEdit,
|
||||||
|
onSuccess: (data) async {
|
||||||
|
refresh(context);
|
||||||
|
showSnackIcon(L10().itemUpdated, success: true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
// TODO: Barcode actions?
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
List<Widget> actions = [];
|
||||||
|
|
||||||
|
if (widget.manufacturerPart.canEdit) {
|
||||||
|
actions.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(TablerIcons.edit),
|
||||||
|
tooltip: L10().edit,
|
||||||
|
onPressed: () {
|
||||||
|
editManufacturerPart(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build a set of tiles to display for this ManufacturerPart instance
|
||||||
|
*/
|
||||||
|
@override
|
||||||
|
List<Widget> getTiles(BuildContext context) {
|
||||||
|
List<Widget> tiles = [];
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
tiles.add(progressIndicator());
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal Part
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().internalPart),
|
||||||
|
subtitle: Text(widget.manufacturerPart.partName),
|
||||||
|
leading: Icon(TablerIcons.box, color: COLOR_ACTION),
|
||||||
|
trailing: InvenTreeAPI().getThumbnail(widget.manufacturerPart.partImage),
|
||||||
|
onTap: () async {
|
||||||
|
showLoadingOverlay();
|
||||||
|
final part = await InvenTreePart().get(widget.manufacturerPart.partId);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
|
if (part is InvenTreePart) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => PartDetailWidget(part)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Manufacturer details
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().manufacturer),
|
||||||
|
subtitle: Text(widget.manufacturerPart.manufacturerName),
|
||||||
|
leading: Icon(TablerIcons.building_factory_2, color: COLOR_ACTION),
|
||||||
|
trailing: InvenTreeAPI().getThumbnail(widget.manufacturerPart.manufacturerImage),
|
||||||
|
onTap: () async {
|
||||||
|
showLoadingOverlay();
|
||||||
|
var supplier = await InvenTreeCompany().get(widget.manufacturerPart.manufacturerId);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
|
if (supplier is InvenTreeCompany) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => CompanyDetailWidget(supplier)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// MPN (part number)
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().manufacturerPartNumber),
|
||||||
|
subtitle: Text(widget.manufacturerPart.MPN),
|
||||||
|
leading: Icon(TablerIcons.hash),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Description
|
||||||
|
if (widget.manufacturerPart.description.isNotEmpty) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().description),
|
||||||
|
subtitle: Text(widget.manufacturerPart.description),
|
||||||
|
leading: Icon(TablerIcons.info_circle),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.manufacturerPart.link.isNotEmpty) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(widget.manufacturerPart.link),
|
||||||
|
leading: Icon(TablerIcons.link, color: COLOR_ACTION),
|
||||||
|
onTap: () async {
|
||||||
|
var uri = Uri.tryParse(widget.manufacturerPart.link);
|
||||||
|
if (uri != null && await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,21 +1,23 @@
|
|||||||
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:flutter_tabler_icons/flutter_tabler_icons.dart";
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
|
import "package:url_launcher/url_launcher.dart";
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/barcode/barcode.dart";
|
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
|
import "package:inventree/barcode/barcode.dart";
|
||||||
|
|
||||||
import "package:inventree/inventree/part.dart";
|
import "package:inventree/inventree/part.dart";
|
||||||
import "package:inventree/inventree/company.dart";
|
import "package:inventree/inventree/company.dart";
|
||||||
|
|
||||||
import "package:inventree/widget/company/company_detail.dart";
|
|
||||||
import "package:inventree/widget/part/part_detail.dart";
|
|
||||||
import "package:inventree/widget/progress.dart";
|
import "package:inventree/widget/progress.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
import "package:url_launcher/url_launcher.dart";
|
import "package:inventree/widget/company/company_detail.dart";
|
||||||
|
import "package:inventree/widget/company/manufacturer_part_detail.dart";
|
||||||
|
import "package:inventree/widget/part/part_detail.dart";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -180,7 +182,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().supplierPartNumber),
|
title: Text(L10().supplierPartNumber),
|
||||||
subtitle: Text(widget.supplierPart.SKU),
|
subtitle: Text(widget.supplierPart.SKU),
|
||||||
leading: Icon(TablerIcons.barcode),
|
leading: Icon(TablerIcons.hash),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -210,7 +212,18 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().manufacturerPartNumber),
|
title: Text(L10().manufacturerPartNumber),
|
||||||
subtitle: Text(widget.supplierPart.MPN),
|
subtitle: Text(widget.supplierPart.MPN),
|
||||||
leading: Icon(TablerIcons.barcode),
|
leading: Icon(TablerIcons.hash, color: COLOR_ACTION),
|
||||||
|
onTap: () async {
|
||||||
|
showLoadingOverlay();
|
||||||
|
var manufacturerPart = await InvenTreeManufacturerPart().get(widget.supplierPart.manufacturerPartId);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
|
if (manufacturerPart is InvenTreeManufacturerPart) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => ManufacturerPartDetailWidget(manufacturerPart)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ 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:flutter_tabler_icons/flutter_tabler_icons.dart";
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
|
|
||||||
import "package:inventree/api_form.dart";
|
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/inventree/model.dart";
|
import "package:inventree/inventree/model.dart";
|
||||||
@ -132,72 +131,15 @@ class _POLineDetailWidgetState extends RefreshableState<POLineDetailWidget> {
|
|||||||
|
|
||||||
// Launch a form to 'receive' this line item
|
// Launch a form to 'receive' this line item
|
||||||
Future<void> receiveLineItem(BuildContext context) async {
|
Future<void> receiveLineItem(BuildContext context) async {
|
||||||
|
widget.item.receive(
|
||||||
// Pre-fill the "destination" to receive into
|
|
||||||
int destination = widget.item.destinationId;
|
|
||||||
|
|
||||||
if (destination < 0) {
|
|
||||||
destination = (widget.item.orderDetail["destination"] ?? -1) as int;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct fields to receive
|
|
||||||
Map<String, dynamic> fields = {
|
|
||||||
"line_item": {
|
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
|
||||||
"hidden": true,
|
|
||||||
"value": widget.item.pk,
|
|
||||||
},
|
|
||||||
"quantity": {
|
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
|
||||||
"value": widget.item.outstanding,
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
|
||||||
},
|
|
||||||
"location": {},
|
|
||||||
"batch_code": {
|
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
|
||||||
},
|
|
||||||
"barcode": {
|
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
|
||||||
"type": "barcode",
|
|
||||||
"label": L10().barcodeAssign,
|
|
||||||
"required": false,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (destination > 0) {
|
|
||||||
fields["location"]?["value"] = destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoadingOverlay();
|
|
||||||
var order = await InvenTreePurchaseOrder().get(widget.item.orderId);
|
|
||||||
hideLoadingOverlay();
|
|
||||||
|
|
||||||
if (order is InvenTreePurchaseOrder) {
|
|
||||||
launchApiForm(
|
|
||||||
context,
|
context,
|
||||||
L10().receiveItem,
|
onSuccess: () => {
|
||||||
order.receive_url,
|
showSnackIcon(L10().receivedItem, success: true),
|
||||||
fields,
|
refresh(context)
|
||||||
method: "POST",
|
|
||||||
icon: TablerIcons.transition_right,
|
|
||||||
onSuccess: (data) async {
|
|
||||||
showSnackIcon(L10().receivedItem, success: true);
|
|
||||||
refresh(context);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
showSnackIcon(L10().error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getTiles(BuildContext context) {
|
List<Widget> getTiles(BuildContext context) {
|
||||||
List<Widget> tiles = [];
|
List<Widget> tiles = [];
|
||||||
|
@ -239,7 +239,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
|
|
||||||
if (allowLabelPrinting) {
|
if (allowLabelPrinting) {
|
||||||
|
|
||||||
String model_type = api.supportsModernLabelPrinting ? InvenTreePart().MODEL_TYPE : "part";
|
String model_type = api.supportsModernLabelPrinting ? InvenTreePart.MODEL_TYPE : "part";
|
||||||
String item_key = api.supportsModernLabelPrinting ? "items" : "part";
|
String item_key = api.supportsModernLabelPrinting ? "items" : "part";
|
||||||
|
|
||||||
_labels = await getLabelTemplates(
|
_labels = await getLabelTemplates(
|
||||||
|
@ -246,7 +246,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
|
|
||||||
if (widget.location != null) {
|
if (widget.location != null) {
|
||||||
|
|
||||||
String model_type = api.supportsModernLabelPrinting ? InvenTreeStockLocation().MODEL_TYPE : "location";
|
String model_type = api.supportsModernLabelPrinting ? InvenTreeStockLocation.MODEL_TYPE : "location";
|
||||||
String item_key = api.supportsModernLabelPrinting ? "items" : "location";
|
String item_key = api.supportsModernLabelPrinting ? "items" : "location";
|
||||||
|
|
||||||
_labels = await getLabelTemplates(
|
_labels = await getLabelTemplates(
|
||||||
|
@ -298,7 +298,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
// Request information on labels available for this stock item
|
// Request information on labels available for this stock item
|
||||||
if (allowLabelPrinting) {
|
if (allowLabelPrinting) {
|
||||||
|
|
||||||
String model_type = api.supportsModernLabelPrinting ? InvenTreeStockItem().MODEL_TYPE : "stock";
|
String model_type = api.supportsModernLabelPrinting ? InvenTreeStockItem.MODEL_TYPE : "stock";
|
||||||
String item_key = api.supportsModernLabelPrinting ? "items" : "item";
|
String item_key = api.supportsModernLabelPrinting ? "items" : "item";
|
||||||
|
|
||||||
// Clear the existing labels list
|
// Clear the existing labels list
|
||||||
|
Loading…
x
Reference in New Issue
Block a user