mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-11-04 07:15:46 +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
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
- Add support for ManufacturerPart model
 | 
			
		||||
- Support barcode scanning for ManufacturerPart
 | 
			
		||||
- Fixes barcode scanning bug which prevents scanning of DataMatrix codes
 | 
			
		||||
- Display "destination" information in PurchaseOrder detail view
 | 
			
		||||
- Pre-fill "location" field when receiving items against PurchaseOrder
 | 
			
		||||
- Fix display of part name in PurchaseOrderLineItem list
 | 
			
		||||
- Adds "assigned to me" filter for Purchase Order list
 | 
			
		||||
- Adds "assigned to me" filter for Sales Order list
 | 
			
		||||
- Updated translations
 | 
			
		||||
 | 
			
		||||
### 0.17.0 - December 2024
 | 
			
		||||
---
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,17 @@ import "package:inventree/api.dart";
 | 
			
		||||
import "package:inventree/app_colors.dart";
 | 
			
		||||
import "package:inventree/barcode/barcode.dart";
 | 
			
		||||
import "package:inventree/helpers.dart";
 | 
			
		||||
import "package:inventree/inventree/sales_order.dart";
 | 
			
		||||
import "package:inventree/l10.dart";
 | 
			
		||||
 | 
			
		||||
import "package:inventree/inventree/company.dart";
 | 
			
		||||
import "package:inventree/inventree/part.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/sentry.dart";
 | 
			
		||||
 | 
			
		||||
import "package:inventree/widget/dialogs.dart";
 | 
			
		||||
import "package:inventree/widget/fields.dart";
 | 
			
		||||
import "package:inventree/widget/progress.dart";
 | 
			
		||||
@@ -562,11 +564,17 @@ class APIFormField {
 | 
			
		||||
        Map<String, dynamic> data = item as Map<String, dynamic>;
 | 
			
		||||
 | 
			
		||||
        switch (model) {
 | 
			
		||||
          case "part":
 | 
			
		||||
          case InvenTreePart.MODEL_TYPE:
 | 
			
		||||
            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;
 | 
			
		||||
          case "stocklocation":
 | 
			
		||||
          case InvenTreeStockLocation.MODEL_TYPE:
 | 
			
		||||
            return InvenTreeStockLocation.fromJson(data).pathstring;
 | 
			
		||||
          default:
 | 
			
		||||
            return "itemAsString not implemented for '${model}'";
 | 
			
		||||
@@ -606,10 +614,12 @@ class APIFormField {
 | 
			
		||||
  Map<String, String> _relatedFieldFilters() {
 | 
			
		||||
 | 
			
		||||
    switch (model) {
 | 
			
		||||
      case "supplierpart":
 | 
			
		||||
      case InvenTreeSupplierPart.MODEL_TYPE:
 | 
			
		||||
        return InvenTreeSupplierPart().defaultListFilters();
 | 
			
		||||
      case "stockitem":
 | 
			
		||||
      case InvenTreeStockItem.MODEL_TYPE:
 | 
			
		||||
        return InvenTreeStockItem().defaultListFilters();
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
@@ -643,7 +653,7 @@ class APIFormField {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (model) {
 | 
			
		||||
      case "part":
 | 
			
		||||
      case InvenTreePart.MODEL_TYPE:
 | 
			
		||||
        var part = InvenTreePart.fromJson(data);
 | 
			
		||||
 | 
			
		||||
        return ListTile(
 | 
			
		||||
@@ -657,14 +667,14 @@ class APIFormField {
 | 
			
		||||
          ) : null,
 | 
			
		||||
          leading: extended ? InvenTreeAPI().getThumbnail(part.thumbnail) : null,
 | 
			
		||||
        );
 | 
			
		||||
      case "parttesttemplate":
 | 
			
		||||
      case InvenTreePartTestTemplate.MODEL_TYPE:
 | 
			
		||||
          var template = InvenTreePartTestTemplate.fromJson(data);
 | 
			
		||||
 | 
			
		||||
          return ListTile(
 | 
			
		||||
            title: Text(template.testName),
 | 
			
		||||
            subtitle: Text(template.description),
 | 
			
		||||
          );
 | 
			
		||||
      case "supplierpart":
 | 
			
		||||
      case InvenTreeSupplierPart.MODEL_TYPE:
 | 
			
		||||
        var part = InvenTreeSupplierPart.fromJson(data);
 | 
			
		||||
 | 
			
		||||
        return ListTile(
 | 
			
		||||
@@ -673,7 +683,7 @@ class APIFormField {
 | 
			
		||||
          leading: extended ? InvenTreeAPI().getThumbnail(part.partImage) : null,
 | 
			
		||||
          trailing: extended && part.supplierImage.isNotEmpty ? InvenTreeAPI().getThumbnail(part.supplierImage) : null,
 | 
			
		||||
        );
 | 
			
		||||
      case "partcategory":
 | 
			
		||||
      case InvenTreePartCategory.MODEL_TYPE:
 | 
			
		||||
 | 
			
		||||
        var cat = InvenTreePartCategory.fromJson(data);
 | 
			
		||||
 | 
			
		||||
@@ -687,7 +697,7 @@ class APIFormField {
 | 
			
		||||
            style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal),
 | 
			
		||||
          ) : null,
 | 
			
		||||
        );
 | 
			
		||||
      case "stockitem":
 | 
			
		||||
      case InvenTreeStockItem.MODEL_TYPE:
 | 
			
		||||
        var item = InvenTreeStockItem.fromJson(data);
 | 
			
		||||
 | 
			
		||||
        return ListTile(
 | 
			
		||||
@@ -697,8 +707,7 @@ class APIFormField {
 | 
			
		||||
          leading: InvenTreeAPI().getThumbnail(item.partThumbnail),
 | 
			
		||||
          trailing: Text(item.quantityString()),
 | 
			
		||||
        );
 | 
			
		||||
      case "stocklocation":
 | 
			
		||||
 | 
			
		||||
      case InvenTreeStockLocation.MODEL_TYPE:
 | 
			
		||||
        var loc = InvenTreeStockLocation.fromJson(data);
 | 
			
		||||
 | 
			
		||||
        return ListTile(
 | 
			
		||||
@@ -711,7 +720,7 @@ class APIFormField {
 | 
			
		||||
            style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal),
 | 
			
		||||
          ) : null,
 | 
			
		||||
        );
 | 
			
		||||
      case "salesordershipment":
 | 
			
		||||
      case InvenTreeSalesOrderShipment.MODEL_TYPE:
 | 
			
		||||
        var shipment = InvenTreeSalesOrderShipment.fromJson(data);
 | 
			
		||||
 | 
			
		||||
        return ListTile(
 | 
			
		||||
@@ -733,14 +742,14 @@ class APIFormField {
 | 
			
		||||
          title: Text(name),
 | 
			
		||||
          subtitle: Text(role),
 | 
			
		||||
        );
 | 
			
		||||
      case "company":
 | 
			
		||||
      case InvenTreeCompany.MODEL_TYPE:
 | 
			
		||||
        var company = InvenTreeCompany.fromJson(data);
 | 
			
		||||
        return ListTile(
 | 
			
		||||
            title: Text(company.name),
 | 
			
		||||
            subtitle: extended ? Text(company.description) : null,
 | 
			
		||||
            leading: InvenTreeAPI().getThumbnail(company.thumbnail)
 | 
			
		||||
        );
 | 
			
		||||
      case "projectcode":
 | 
			
		||||
      case InvenTreeProjectCode.MODEL_TYPE:
 | 
			
		||||
        var project_code = InvenTreeProjectCode.fromJson(data);
 | 
			
		||||
        return ListTile(
 | 
			
		||||
            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:inventree/helpers.dart";
 | 
			
		||||
import "package:inventree/inventree/sales_order.dart";
 | 
			
		||||
import "package:inventree/inventree/sentry.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: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/snacks.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";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -176,15 +179,37 @@ class BarcodeScanHandler extends BarcodeHandler {
 | 
			
		||||
   */
 | 
			
		||||
  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().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
 | 
			
		||||
@@ -218,20 +243,23 @@ class BarcodeScanHandler extends BarcodeHandler {
 | 
			
		||||
 | 
			
		||||
    // The following model types can be matched with barcodes
 | 
			
		||||
    List<String> validModels = [
 | 
			
		||||
      "part",
 | 
			
		||||
      "stockitem",
 | 
			
		||||
      "stocklocation",
 | 
			
		||||
      "supplierpart",
 | 
			
		||||
      InvenTreePart.MODEL_TYPE,
 | 
			
		||||
      InvenTreeCompany.MODEL_TYPE,
 | 
			
		||||
      InvenTreeStockItem.MODEL_TYPE,
 | 
			
		||||
      InvenTreeStockLocation.MODEL_TYPE,
 | 
			
		||||
      InvenTreeSupplierPart.MODEL_TYPE,
 | 
			
		||||
      InvenTreeManufacturerPart.MODEL_TYPE,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if (InvenTreeAPI().supportsOrderBarcodes) {
 | 
			
		||||
      validModels.add("purchaseorder");
 | 
			
		||||
      validModels.add("salesorder");
 | 
			
		||||
      validModels.add(InvenTreePurchaseOrder.MODEL_TYPE);
 | 
			
		||||
      validModels.add(InvenTreeSalesOrder.MODEL_TYPE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (var key in validModels) {
 | 
			
		||||
      if (data.containsKey(key)) {
 | 
			
		||||
        try {
 | 
			
		||||
          pk = (data[key]?["pk"] ?? -1) as int;
 | 
			
		||||
 | 
			
		||||
          // Break on the first valid match found
 | 
			
		||||
@@ -239,6 +267,9 @@ class BarcodeScanHandler extends BarcodeHandler {
 | 
			
		||||
            model = key;
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        } catch (error, stackTrace) {
 | 
			
		||||
          sentryReportError("onBarcodeMatched", error, stackTrace);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -248,25 +279,30 @@ class BarcodeScanHandler extends BarcodeHandler {
 | 
			
		||||
      barcodeSuccessTone();
 | 
			
		||||
 | 
			
		||||
      switch (model) {
 | 
			
		||||
        case "part":
 | 
			
		||||
          await handlePart(pk);
 | 
			
		||||
          return;
 | 
			
		||||
        case "stockitem":
 | 
			
		||||
        case InvenTreeStockItem.MODEL_TYPE:
 | 
			
		||||
          await handleStockItem(pk);
 | 
			
		||||
          return;
 | 
			
		||||
        case "stocklocation":
 | 
			
		||||
          await handleStockLocation(pk);
 | 
			
		||||
          return;
 | 
			
		||||
        case "supplierpart":
 | 
			
		||||
          await handleSupplierPart(pk);
 | 
			
		||||
          return;
 | 
			
		||||
        case "purchaseorder":
 | 
			
		||||
        case InvenTreePurchaseOrder.MODEL_TYPE:
 | 
			
		||||
          await handlePurchaseOrder(pk);
 | 
			
		||||
          return;
 | 
			
		||||
        case "salesorder":
 | 
			
		||||
        case InvenTreeSalesOrder.MODEL_TYPE:
 | 
			
		||||
          await handleSalesOrder(pk);
 | 
			
		||||
          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:
 | 
			
		||||
          // Fall through to failure state
 | 
			
		||||
          break;
 | 
			
		||||
@@ -324,21 +360,6 @@ class UniqueBarcodeHandler extends BarcodeHandler {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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")) {
 | 
			
		||||
      showServerError(
 | 
			
		||||
        "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
 | 
			
		||||
   */
 | 
			
		||||
  void _onScanSuccess(Code? code) {
 | 
			
		||||
  Future<void> onScanSuccess(Code? code) async {
 | 
			
		||||
 | 
			
		||||
    if (scanning_paused) {
 | 
			
		||||
      return;
 | 
			
		||||
@@ -122,18 +122,16 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
 | 
			
		||||
    if (mounted) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        scanned_code = barcode;
 | 
			
		||||
        scanning_paused = barcode.isNotEmpty;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (barcode.isNotEmpty) {
 | 
			
		||||
 | 
			
		||||
      handleBarcodeData(barcode).then((_) {
 | 
			
		||||
      pauseScan();
 | 
			
		||||
 | 
			
		||||
      await handleBarcodeData(barcode).then((_) {
 | 
			
		||||
        if (!single_scanning && mounted) {
 | 
			
		||||
          // Resume next scan
 | 
			
		||||
          setState(() {
 | 
			
		||||
            scanning_paused = false;
 | 
			
		||||
          });
 | 
			
		||||
          resumeScan();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
@@ -186,7 +184,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
 | 
			
		||||
  Widget BarcodeReader(BuildContext context) {
 | 
			
		||||
 | 
			
		||||
    return ReaderWidget(
 | 
			
		||||
      onScan: _onScanSuccess,
 | 
			
		||||
      onScan: onScanSuccess,
 | 
			
		||||
      isMultiScan: false,
 | 
			
		||||
      tryHarder: true,
 | 
			
		||||
      tryInverted: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,10 @@ class BarcodeHandler {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var response = await InvenTreeAPI().post(
 | 
			
		||||
    APIResponse? response;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      response = await InvenTreeAPI().post(
 | 
			
		||||
        url,
 | 
			
		||||
        body: {
 | 
			
		||||
          "barcode": barcode,
 | 
			
		||||
@@ -87,6 +90,16 @@ class BarcodeHandler {
 | 
			
		||||
        },
 | 
			
		||||
        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());
 | 
			
		||||
 | 
			
		||||
@@ -94,7 +107,7 @@ class BarcodeHandler {
 | 
			
		||||
 | 
			
		||||
    // Handle strange response from the server
 | 
			
		||||
    if (!response.isValid() || !response.isMap()) {
 | 
			
		||||
      onBarcodeUnknown({});
 | 
			
		||||
      await onBarcodeUnknown({});
 | 
			
		||||
 | 
			
		||||
      showSnackIcon(L10().serverError, success: false);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
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:inventree/l10.dart";
 | 
			
		||||
import "package:inventree/api_form.dart";
 | 
			
		||||
 | 
			
		||||
import "package:inventree/barcode/barcode.dart";
 | 
			
		||||
import "package:inventree/barcode/handler.dart";
 | 
			
		||||
@@ -23,10 +21,11 @@ import "package:inventree/widget/snacks.dart";
 | 
			
		||||
 */
 | 
			
		||||
class POReceiveBarcodeHandler extends BarcodeHandler {
 | 
			
		||||
 | 
			
		||||
  POReceiveBarcodeHandler({this.purchaseOrder, this.location});
 | 
			
		||||
  POReceiveBarcodeHandler({this.purchaseOrder, this.location, this.lineItem});
 | 
			
		||||
 | 
			
		||||
  InvenTreePurchaseOrder? purchaseOrder;
 | 
			
		||||
  InvenTreeStockLocation? location;
 | 
			
		||||
  InvenTreePOLineItem? lineItem;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String getOverlayText(BuildContext context) => L10().barcodeReceivePart;
 | 
			
		||||
@@ -34,11 +33,15 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
 | 
			
		||||
  @override
 | 
			
		||||
  Future<void> processBarcode(String barcode,
 | 
			
		||||
      {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 = {
 | 
			
		||||
      "purchase_order": purchaseOrder?.pk,
 | 
			
		||||
      "location": location?.pk,
 | 
			
		||||
      "line_item": lineItem?.pk,
 | 
			
		||||
      "auto_allocate": !confirm,
 | 
			
		||||
      ...extra_data,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -47,11 +50,13 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    barcodeSuccess(L10().receivedItem);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -66,47 +71,39 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
 | 
			
		||||
      showSnackIcon(L10().missingData, success: false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Construct fields to receive
 | 
			
		||||
    Map<String, dynamic> fields = {
 | 
			
		||||
      "line_item": {
 | 
			
		||||
        "parent": "items",
 | 
			
		||||
        "nested": true,
 | 
			
		||||
        "hidden": true,
 | 
			
		||||
        "value": lineItemData["pk"] as int,
 | 
			
		||||
      },
 | 
			
		||||
      "quantity": {
 | 
			
		||||
        "parent": "items",
 | 
			
		||||
        "nested": true,
 | 
			
		||||
        "value": lineItemData["quantity"] as double?,
 | 
			
		||||
      },
 | 
			
		||||
      "status": {
 | 
			
		||||
        "parent": "items",
 | 
			
		||||
        "nested": true,
 | 
			
		||||
      },
 | 
			
		||||
      "location": {
 | 
			
		||||
        "value": lineItemData["location"] as int?,
 | 
			
		||||
      },
 | 
			
		||||
      "barcode": {
 | 
			
		||||
        "parent": "items",
 | 
			
		||||
        "nested": true,
 | 
			
		||||
        "hidden": true,
 | 
			
		||||
        "type": "barcode",
 | 
			
		||||
        "value": data["barcode_data"] as String,
 | 
			
		||||
    // At minimum, we need the line item ID value
 | 
			
		||||
    final int? lineItemId = lineItemData["pk"] as int?;
 | 
			
		||||
 | 
			
		||||
    if (lineItemId == null) {
 | 
			
		||||
      barcodeFailureTone();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    final context = OneContext().context!;
 | 
			
		||||
    final purchase_order_pk = lineItemData["purchase_order"];
 | 
			
		||||
    final receive_url = "${InvenTreePurchaseOrder().URL}${purchase_order_pk}/receive/";
 | 
			
		||||
    InvenTreePOLineItem? lineItem = await InvenTreePOLineItem().get(lineItemId) as InvenTreePOLineItem?;
 | 
			
		||||
 | 
			
		||||
    launchApiForm(
 | 
			
		||||
        context,
 | 
			
		||||
        L10().receiveItem,
 | 
			
		||||
        receive_url,
 | 
			
		||||
        fields,
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        icon: TablerIcons.transition_right,
 | 
			
		||||
        onSuccess: (data) async {
 | 
			
		||||
    if (lineItem == null) {
 | 
			
		||||
      barcodeFailureTone();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Next, extract the "optional" fields
 | 
			
		||||
 | 
			
		||||
    // Extract information from the returned server response
 | 
			
		||||
    double? quantity = double.tryParse((lineItemData["quantity"] ?? "0").toString());
 | 
			
		||||
    int? destination = lineItemData["location"] as int?;
 | 
			
		||||
    String? barcode = data["barcode_data"] as String?;
 | 
			
		||||
 | 
			
		||||
    // Discard the barcode scanner at this stage
 | 
			
		||||
    if (OneContext.hasContext) {
 | 
			
		||||
      OneContext().pop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await lineItem.receive(
 | 
			
		||||
      OneContext().context!,
 | 
			
		||||
      destination: destination,
 | 
			
		||||
      quantity: quantity,
 | 
			
		||||
      barcode: barcode,
 | 
			
		||||
      onSuccess: () {
 | 
			
		||||
        showSnackIcon(L10().receivedItem, success: true);
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ class BarcodeScanStockLocationHandler extends BarcodeHandler {
 | 
			
		||||
 | 
			
		||||
    // We expect that the barcode points to a '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!
 | 
			
		||||
      if (_loc > 0) {
 | 
			
		||||
@@ -83,7 +83,7 @@ class BarcodeScanStockItemHandler extends BarcodeHandler {
 | 
			
		||||
  Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
 | 
			
		||||
    // We expect that the barcode points to a '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!
 | 
			
		||||
      if (_item > 0) {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,8 @@ class InvenTreeCompany extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "company/";
 | 
			
		||||
 | 
			
		||||
  static const String MODEL_TYPE = "company";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"];
 | 
			
		||||
 | 
			
		||||
@@ -128,6 +130,8 @@ class InvenTreeSupplierPart extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "company/part/";
 | 
			
		||||
 | 
			
		||||
  static const String MODEL_TYPE = "supplierpart";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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 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");
 | 
			
		||||
  
 | 
			
		||||
@@ -179,7 +183,7 @@ class InvenTreeSupplierPart extends InvenTreeModel {
 | 
			
		||||
  
 | 
			
		||||
  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");
 | 
			
		||||
 | 
			
		||||
@@ -219,19 +223,50 @@ class InvenTreeManufacturerPart extends InvenTreeModel {
 | 
			
		||||
  InvenTreeManufacturerPart.fromJson(Map<String, dynamic> json) : super.fromJson(json);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String url = "company/part/manufacturer/";
 | 
			
		||||
  String URL = "company/part/manufacturer/";
 | 
			
		||||
 | 
			
		||||
  static const String MODEL_TYPE = "manufacturerpart";
 | 
			
		||||
 | 
			
		||||
  @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 {
 | 
			
		||||
      "manufacturer_detail": "true",
 | 
			
		||||
      "part_detail": "true",
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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");
 | 
			
		||||
 | 
			
		||||
  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");
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class InvenTreeModel {
 | 
			
		||||
  String get WEB_URL => "";
 | 
			
		||||
 | 
			
		||||
  // 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
 | 
			
		||||
  void setValue(String key, dynamic value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,8 @@ class InvenTreePartCategory extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "part/category/";
 | 
			
		||||
 | 
			
		||||
  static const String MODEL_TYPE = "partcategory";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> get rolesRequired => ["part_category"];
 | 
			
		||||
 | 
			
		||||
@@ -79,6 +81,8 @@ class InvenTreePartTestTemplate extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "part/test-template/";
 | 
			
		||||
 | 
			
		||||
  static const String MODEL_TYPE = "parttesttemplate";
 | 
			
		||||
 | 
			
		||||
  String get key => getString("key");
 | 
			
		||||
 | 
			
		||||
  String get testName => getString("test_name");
 | 
			
		||||
@@ -192,8 +196,7 @@ class InvenTreePart extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "part/";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String get MODEL_TYPE => "part";
 | 
			
		||||
  static const String MODEL_TYPE = "part";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> get rolesRequired => ["part"];
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@ class InvenTreeProjectCode extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "project-code/";
 | 
			
		||||
 | 
			
		||||
  static const String MODEL_TYPE = "projectcode";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Map<String, Map<String, dynamic>> formFields() {
 | 
			
		||||
    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/helpers.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/widget/progress.dart";
 | 
			
		||||
 | 
			
		||||
import "package:inventree/api_form.dart";
 | 
			
		||||
import "package:inventree/l10.dart";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Class representing an individual PurchaseOrder instance
 | 
			
		||||
@@ -21,8 +26,7 @@ class InvenTreePurchaseOrder extends InvenTreeOrder {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "order/po/";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String get MODEL_TYPE => "purchaseorder";
 | 
			
		||||
  static const String MODEL_TYPE = "purchaseorder";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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");
 | 
			
		||||
 | 
			
		||||
  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 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
 | 
			
		||||
  String get URL => "order/so/";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String get MODEL_TYPE => "salesorder";
 | 
			
		||||
  static const String MODEL_TYPE = "salesorder";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> get rolesRequired => ["sales_order"];
 | 
			
		||||
@@ -250,6 +249,8 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "/order/so/shipment/";
 | 
			
		||||
 | 
			
		||||
  static const String MODEL_TYPE = "salesordershipment";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Map<String, Map<String, dynamic>> formFields() {
 | 
			
		||||
    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 {
 | 
			
		||||
 | 
			
		||||
  if (sentryIgnoreError(error)) {
 | 
			
		||||
    // No action on this error
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  print("----- Sentry Intercepted error: $error -----");
 | 
			
		||||
  print(stackTrace);
 | 
			
		||||
 | 
			
		||||
@@ -228,3 +233,18 @@ Future<void> sentryReportError(String source, dynamic error, StackTrace? stackTr
 | 
			
		||||
    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/";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Map<String, String> defaultListFilters() {
 | 
			
		||||
  Map<String, String> defaultFilters() {
 | 
			
		||||
 | 
			
		||||
    // By default, order by decreasing date
 | 
			
		||||
    return {
 | 
			
		||||
@@ -168,8 +168,7 @@ class InvenTreeStockItem extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "stock/";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String get MODEL_TYPE => "stockitem";
 | 
			
		||||
  static const String MODEL_TYPE = "stockitem";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> get rolesRequired => ["stock"];
 | 
			
		||||
@@ -206,7 +205,7 @@ class InvenTreeStockItem extends InvenTreeModel {
 | 
			
		||||
 | 
			
		||||
    if (isSerialized()) {
 | 
			
		||||
      // 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
 | 
			
		||||
@@ -395,7 +394,7 @@ class InvenTreeStockItem extends InvenTreeModel {
 | 
			
		||||
 | 
			
		||||
      // Use the detailed part information as priority
 | 
			
		||||
      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
 | 
			
		||||
@@ -411,7 +410,7 @@ class InvenTreeStockItem extends InvenTreeModel {
 | 
			
		||||
 | 
			
		||||
      // Use the detailed part description as priority
 | 
			
		||||
      if (jsondata.containsKey("part_detail")) {
 | 
			
		||||
        desc = (jsondata["part_detail"]["description"] ?? "") as String;
 | 
			
		||||
        desc = (jsondata["part_detail"]?["description"] ?? "") as String;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (desc.isEmpty) {
 | 
			
		||||
@@ -425,7 +424,7 @@ class InvenTreeStockItem extends InvenTreeModel {
 | 
			
		||||
      String img = "";
 | 
			
		||||
 | 
			
		||||
      if (jsondata.containsKey("part_detail")) {
 | 
			
		||||
        img = (jsondata["part_detail"]["thumbnail"] ?? "") as String;
 | 
			
		||||
        img = (jsondata["part_detail"]?["thumbnail"] ?? "") as String;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (img.isEmpty) {
 | 
			
		||||
@@ -468,7 +467,7 @@ class InvenTreeStockItem extends InvenTreeModel {
 | 
			
		||||
      if (jsondata.containsKey("supplier_part_detail")) {
 | 
			
		||||
        thumb = (jsondata["supplier_part_detail"]?["supplier_detail"]?["image"] ?? "") as String;
 | 
			
		||||
      } else if (jsondata.containsKey("supplier_detail")) {
 | 
			
		||||
        thumb = (jsondata["supplier_detail"]["image"] ?? "") as String;
 | 
			
		||||
        thumb = (jsondata["supplier_detail"]?["image"] ?? "") as String;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return thumb;
 | 
			
		||||
@@ -681,8 +680,7 @@ class InvenTreeStockLocation extends InvenTreeModel {
 | 
			
		||||
  @override
 | 
			
		||||
  String get URL => "stock/location/";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String get MODEL_TYPE => "stocklocation";
 | 
			
		||||
  static const String MODEL_TYPE = "stocklocation";
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> get rolesRequired => ["stock_location"];
 | 
			
		||||
 
 | 
			
		||||
@@ -608,6 +608,9 @@
 | 
			
		||||
  "itemDeleted": "Item has been removed",
 | 
			
		||||
  "@itemDeleted": {},
 | 
			
		||||
 | 
			
		||||
  "itemUpdated": "Item updated",
 | 
			
		||||
  "@itemUpdated": {},
 | 
			
		||||
 | 
			
		||||
  "keywords": "Keywords",
 | 
			
		||||
  "@keywords": {},
 | 
			
		||||
 | 
			
		||||
@@ -684,6 +687,12 @@
 | 
			
		||||
  "lost": "Lost",
 | 
			
		||||
  "@lost": {},
 | 
			
		||||
 | 
			
		||||
  "manufacturerPart": "Manufacturer Part",
 | 
			
		||||
  "@manufacturerPart": {},
 | 
			
		||||
 | 
			
		||||
  "manufacturerPartEdit": "Edit Manufacturer Part",
 | 
			
		||||
  "@manufacturerPartEdit": {},
 | 
			
		||||
 | 
			
		||||
  "manufacturerPartNumber": "Manufacturer Part Number",
 | 
			
		||||
  "@manufacturerPartNumber": {},
 | 
			
		||||
 | 
			
		||||
@@ -915,6 +924,12 @@
 | 
			
		||||
  "projectCode": "Project Code",
 | 
			
		||||
  "@projectCode": {},
 | 
			
		||||
 | 
			
		||||
    "purchaseOrderConfirmScan": "Confirm Scan Data",
 | 
			
		||||
    "@purchaseOrderConfirmScan": {},
 | 
			
		||||
 | 
			
		||||
    "purchaseOrderConfirmScanDetail": "Confirm details when scanning in items",
 | 
			
		||||
    "@purchaseOrderConfirmScanDetail": {},
 | 
			
		||||
 | 
			
		||||
   "purchaseOrderEnable": "Enable Purchase Orders",
 | 
			
		||||
   "@purchaseOrderEnable": {},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ const String INV_STOCK_CONFIRM_SCAN = "stockConfirmScan";
 | 
			
		||||
// Purchase order settings
 | 
			
		||||
const String INV_PO_ENABLE = "poEnable";
 | 
			
		||||
const String INV_PO_SHOW_CAMERA = "poShowCamera";
 | 
			
		||||
const String INV_PO_CONFIRM_SCAN = "poConfirmScan";
 | 
			
		||||
 | 
			
		||||
// Sales order settings
 | 
			
		||||
const String INV_SO_ENABLE = "soEnable";
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ class _InvenTreePurchaseOrderSettingsState extends State<InvenTreePurchaseOrderS
 | 
			
		||||
 | 
			
		||||
  bool poEnable = true;
 | 
			
		||||
  bool poShowCamera = true;
 | 
			
		||||
  bool poConfirmScan = true;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
@@ -30,6 +31,7 @@ class _InvenTreePurchaseOrderSettingsState extends State<InvenTreePurchaseOrderS
 | 
			
		||||
  Future<void> loadSettings() async {
 | 
			
		||||
    poEnable = await InvenTreeSettingsManager().getBool(INV_PO_ENABLE, true);
 | 
			
		||||
    poShowCamera = await InvenTreeSettingsManager().getBool(INV_PO_SHOW_CAMERA, true);
 | 
			
		||||
    poConfirmScan = await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true);
 | 
			
		||||
 | 
			
		||||
    if (mounted) {
 | 
			
		||||
      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_speed_dial/flutter_speed_dial.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/app_colors.dart";
 | 
			
		||||
import "package:inventree/barcode/barcode.dart";
 | 
			
		||||
import "package:inventree/l10.dart";
 | 
			
		||||
 | 
			
		||||
import "package:inventree/barcode/barcode.dart";
 | 
			
		||||
 | 
			
		||||
import "package:inventree/inventree/part.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/refreshable_state.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(
 | 
			
		||||
          title: Text(L10().supplierPartNumber),
 | 
			
		||||
          subtitle: Text(widget.supplierPart.SKU),
 | 
			
		||||
          leading: Icon(TablerIcons.barcode),
 | 
			
		||||
          leading: Icon(TablerIcons.hash),
 | 
			
		||||
        )
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -210,7 +212,18 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
 | 
			
		||||
        ListTile(
 | 
			
		||||
          title: Text(L10().manufacturerPartNumber),
 | 
			
		||||
          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_tabler_icons/flutter_tabler_icons.dart";
 | 
			
		||||
 | 
			
		||||
import "package:inventree/api_form.dart";
 | 
			
		||||
import "package:inventree/app_colors.dart";
 | 
			
		||||
import "package:inventree/helpers.dart";
 | 
			
		||||
import "package:inventree/inventree/model.dart";
 | 
			
		||||
@@ -132,70 +131,13 @@ class _POLineDetailWidgetState extends RefreshableState<POLineDetailWidget> {
 | 
			
		||||
 | 
			
		||||
    // Launch a form to 'receive' this line item
 | 
			
		||||
  Future<void> receiveLineItem(BuildContext context) async {
 | 
			
		||||
 | 
			
		||||
    // 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(
 | 
			
		||||
    widget.item.receive(
 | 
			
		||||
      context,
 | 
			
		||||
      L10().receiveItem,
 | 
			
		||||
      order.receive_url,
 | 
			
		||||
      fields,
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      icon: TablerIcons.transition_right,
 | 
			
		||||
      onSuccess: (data) async {
 | 
			
		||||
        showSnackIcon(L10().receivedItem, success: true);
 | 
			
		||||
        refresh(context);
 | 
			
		||||
      onSuccess: () => {
 | 
			
		||||
        showSnackIcon(L10().receivedItem, success: true),
 | 
			
		||||
        refresh(context)
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
    } else {
 | 
			
		||||
      showSnackIcon(L10().error);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @override
 | 
			
		||||
 
 | 
			
		||||
@@ -239,7 +239,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
 | 
			
		||||
 | 
			
		||||
    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";
 | 
			
		||||
 | 
			
		||||
      _labels = await getLabelTemplates(
 | 
			
		||||
 
 | 
			
		||||
@@ -246,7 +246,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
 | 
			
		||||
 | 
			
		||||
      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";
 | 
			
		||||
 | 
			
		||||
        _labels = await getLabelTemplates(
 | 
			
		||||
 
 | 
			
		||||
@@ -298,7 +298,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
 | 
			
		||||
    // Request information on labels available for this stock item
 | 
			
		||||
    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";
 | 
			
		||||
 | 
			
		||||
      // Clear the existing labels list
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user