mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-30 21:05:42 +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:
		| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user