2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 13:36:50 +00:00

Add support for company attachments (#261)

* Add support for company attachments

- Add API version check
- Add new class
- Add link to company detail page
- Assorted refactoring

* linting fixes
This commit is contained in:
Oliver 2023-02-16 22:50:32 +11:00 committed by GitHub
parent c7527e8b4e
commit 9485d858eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 35 deletions

View File

@ -1,6 +1,11 @@
## InvenTree App Release Notes ## InvenTree App Release Notes
--- ---
### 0.11.0 -
---
- Add support for attachments on Companies
### 0.10.0 - February 2023 ### 0.10.0 - February 2023
--- ---

View File

@ -258,17 +258,26 @@ class InvenTreeAPI {
int get apiVersion => _apiVersion; int get apiVersion => _apiVersion;
// API endpoint for receiving purchase order line items was introduced in v12
bool get supportsPoReceive => apiVersion >= 12;
// Notification support requires API v25 or newer // Notification support requires API v25 or newer
bool get supportsNotifications => isConnected() && apiVersion >= 25; bool get supportsNotifications => isConnected() && apiVersion >= 25;
// Return True if the API supports 'settings' (requires API v46)
bool get supportsSettings => isConnected() && apiVersion >= 46;
// Part parameter support requires API v56 or newer
bool get supportsPartParameters => isConnected() && apiVersion >= 56;
// Supports 'modern' barcode API (v80 or newer) // Supports 'modern' barcode API (v80 or newer)
bool get supportModernBarcodes => isConnected() && apiVersion >= 80; bool get supportModernBarcodes => isConnected() && apiVersion >= 80;
// Structural categories requires API v83 or newer // Structural categories requires API v83 or newer
bool get supportsStructuralCategories => isConnected() && apiVersion >= 83; bool get supportsStructuralCategories => isConnected() && apiVersion >= 83;
// Part parameter support requires API v56 or newer // Company attachments require API v95 or newer
bool get supportsPartParameters => isConnected() && apiVersion >= 56; bool get supportCompanyAttachments => isConnected() && apiVersion >= 95;
// Are plugins enabled on the server? // Are plugins enabled on the server?
bool _pluginsEnabled = false; bool _pluginsEnabled = false;
@ -322,9 +331,6 @@ class InvenTreeAPI {
// Ensure we only ever create a single instance of the API class // Ensure we only ever create a single instance of the API class
static final InvenTreeAPI _api = InvenTreeAPI._internal(); static final InvenTreeAPI _api = InvenTreeAPI._internal();
// API endpoint for receiving purchase order line items was introduced in v12
bool get supportsPoReceive => apiVersion >= 12;
/* /*
* Connect to the remote InvenTree server: * Connect to the remote InvenTree server:
* *
@ -1280,9 +1286,6 @@ class InvenTreeAPI {
); );
} }
// Return True if the API supports 'settings' (requires API v46)
bool get supportsSettings => isConnected() && apiVersion >= 46;
// Keep a record of which settings we have received from the server // Keep a record of which settings we have received from the server
Map<String, InvenTreeGlobalSetting> _globalSettings = {}; Map<String, InvenTreeGlobalSetting> _globalSettings = {};
Map<String, InvenTreeUserSetting> _userSettings = {}; Map<String, InvenTreeUserSetting> _userSettings = {};

View File

@ -86,6 +86,26 @@ class InvenTreeCompany extends InvenTreeModel {
} }
/*
* Class representing an attachment file against a Company object
*/
class InvenTreeCompanyAttachment extends InvenTreeAttachment {
InvenTreeCompanyAttachment() : super();
InvenTreeCompanyAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
String get REFERENCE_FIELD => "company";
@override
String get URL => "company/attachment/";
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeCompanyAttachment.fromJson(json);
}
/* /*
* The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database * The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database
*/ */

View File

@ -134,7 +134,6 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
widget.attachment.REFERENCE_FIELD: widget.referenceId.toString() widget.attachment.REFERENCE_FIELD: widget.referenceId.toString()
} }
).then((var results) { ).then((var results) {
attachments.clear(); attachments.clear();
for (var result in results) { for (var result in results) {

View File

@ -8,12 +8,12 @@ import "package:inventree/app_colors.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/widget/attachment_widget.dart";
import "package:inventree/widget/purchase_order_list.dart"; import "package:inventree/widget/purchase_order_list.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/supplier_part_list.dart"; import "package:inventree/widget/supplier_part_list.dart";
/* /*
* Widget for displaying detail view of a single Company instance * Widget for displaying detail view of a single Company instance
*/ */
@ -37,6 +37,8 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
int supplierPartCount = 0; int supplierPartCount = 0;
int attachmentCount = 0;
@override @override
String getAppBarTitle(BuildContext context) => L10().company; String getAppBarTitle(BuildContext context) => L10().company;
@ -70,7 +72,6 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
@override @override
Future<void> request(BuildContext context) async { Future<void> request(BuildContext context) async {
final bool result = await widget.company.reload(); final bool result = await widget.company.reload();
if (!result || widget.company.pk <= 0) { if (!result || widget.company.pk <= 0) {
@ -80,7 +81,8 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
} }
if (widget.company.isSupplier) { if (widget.company.isSupplier) {
outstandingOrders = await widget.company.getPurchaseOrders(outstanding: true); outstandingOrders =
await widget.company.getPurchaseOrders(outstanding: true);
} }
InvenTreeSupplierPart().count( InvenTreeSupplierPart().count(
@ -94,6 +96,20 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
}); });
} }
}); });
if (api.supportCompanyAttachments) {
InvenTreeCompanyAttachment().count(
filters: {
"company": widget.company.pk.toString()
}
).then((value) {
if (mounted) {
setState(() {
attachmentCount = value;
});
}
});
}
} }
Future <void> editCompany(BuildContext context) async { Future <void> editCompany(BuildContext context) async {
@ -251,6 +267,26 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
)); ));
} }
if (api.supportCompanyAttachments) {
tiles.add(ListTile(
title: Text(L10().attachments),
leading: FaIcon(FontAwesomeIcons.fileLines, color: COLOR_CLICK),
trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreeCompanyAttachment(),
widget.company.pk,
api.checkPermission("purchase_order", "change") || api.checkPermission("sales_order", "change")
)
)
);
}
));
}
return tiles; return tiles;
} }

View File

@ -2,7 +2,6 @@ import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart";
import "package:inventree/app_colors.dart"; import "package:inventree/app_colors.dart";
import "package:inventree/barcode.dart"; import "package:inventree/barcode.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
@ -51,7 +50,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
if (location != null) { if (location != null) {
// Add "locate" button // Add "locate" button
if (InvenTreeAPI().supportsMixin("locate")) { if (api.supportsMixin("locate")) {
actions.add( actions.add(
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.magnifyingGlassLocation), icon: FaIcon(FontAwesomeIcons.magnifyingGlassLocation),
@ -64,7 +63,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
} }
// Add "edit" button // Add "edit" button
if (InvenTreeAPI().checkPermission("stock_location", "change")) { if (api.checkPermission("stock_location", "change")) {
actions.add( actions.add(
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.penToSquare), icon: FaIcon(FontAwesomeIcons.penToSquare),
@ -86,7 +85,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
final _loc = location; final _loc = location;
if (_loc != null) { if (_loc != null) {
InvenTreeAPI().locateItemOrLocation(context, location: _loc.pk); api.locateItemOrLocation(context, location: _loc.pk);
} }
} }
@ -372,7 +371,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
tiles.add(locationDescriptionCard(includeActions: false)); tiles.add(locationDescriptionCard(includeActions: false));
if (InvenTreeAPI().checkPermission("stock", "add")) { if (api.checkPermission("stock", "add")) {
tiles.add( tiles.add(
ListTile( ListTile(
@ -403,7 +402,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
if (location != null) { if (location != null) {
// Scan stock item into location // Scan stock item into location
if (InvenTreeAPI().checkPermission("stock", "change")) { if (api.checkPermission("stock", "change")) {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().barcodeScanItem), title: Text(L10().barcodeScanItem),
@ -429,7 +428,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
); );
// Scan this location into another one // Scan this location into another one
if (InvenTreeAPI().checkPermission("stock_location", "change")) { if (api.checkPermission("stock_location", "change")) {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().transferStockLocation), title: Text(L10().transferStockLocation),
@ -454,7 +453,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
); );
} }
if (InvenTreeAPI().supportModernBarcodes) { if (api.supportModernBarcodes) {
tiles.add( tiles.add(
customBarcodeActionTile(context, this, location!.customBarcode, "stocklocation", location!.pk) customBarcodeActionTile(context, this, location!.customBarcode, "stocklocation", location!.pk)
); );

View File

@ -2,7 +2,6 @@ import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart";
import "package:inventree/app_colors.dart"; import "package:inventree/app_colors.dart";
import "package:inventree/barcode.dart"; import "package:inventree/barcode.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
@ -73,7 +72,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
List<Widget> actions = []; List<Widget> actions = [];
if (InvenTreeAPI().checkPermission("part", "view")) { if (api.checkPermission("part", "view")) {
actions.add( actions.add(
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.globe), icon: FaIcon(FontAwesomeIcons.globe),
@ -82,7 +81,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
); );
} }
if (InvenTreeAPI().checkPermission("part", "change")) { if (api.checkPermission("part", "change")) {
actions.add( actions.add(
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.penToSquare), icon: FaIcon(FontAwesomeIcons.penToSquare),
@ -144,7 +143,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
}); });
// Request the number of parameters for this part // Request the number of parameters for this part
if (InvenTreeAPI().supportsPartParameters) { if (api.supportsPartParameters) {
showParameters = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_PARAMETERS, true) as bool; showParameters = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_PARAMETERS, true) as bool;
@ -222,7 +221,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
*/ */
Future <void> _toggleStar(BuildContext context) async { Future <void> _toggleStar(BuildContext context) async {
if (InvenTreeAPI().checkPermission("part", "view")) { if (api.checkPermission("part", "view")) {
showLoadingOverlay(context); showLoadingOverlay(context);
await part.update(values: {"starred": "${!part.starred}"}); await part.update(values: {"starred": "${!part.starred}"});
hideLoadingOverlay(); hideLoadingOverlay();
@ -256,7 +255,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
}, },
), ),
leading: GestureDetector( leading: GestureDetector(
child: InvenTreeAPI().getImage(part.thumbnail), child: api.getImage(part.thumbnail),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
@ -316,7 +315,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
ListTile( ListTile(
title: Text(L10().templatePart), title: Text(L10().templatePart),
subtitle: Text(parentPart!.fullname), subtitle: Text(parentPart!.fullname),
leading: InvenTreeAPI().getImage( leading: api.getImage(
parentPart!.thumbnail, parentPart!.thumbnail,
width: 32, width: 32,
height: 32, height: 32,
@ -589,7 +588,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
builder: (context) => AttachmentWidget( builder: (context) => AttachmentWidget(
InvenTreePartAttachment(), InvenTreePartAttachment(),
part.pk, part.pk,
InvenTreeAPI().checkPermission("part", "change")) api.checkPermission("part", "change"))
) )
); );
}, },
@ -646,7 +645,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
if (part.isTrackable) { if (part.isTrackable) {
// read the next available serial number // read the next available serial number
showLoadingOverlay(context); showLoadingOverlay(context);
var response = await InvenTreeAPI().get("/api/part/${part.pk}/serial-numbers/", expectedStatusCode: null); var response = await api.get("/api/part/${part.pk}/serial-numbers/", expectedStatusCode: null);
hideLoadingOverlay(); hideLoadingOverlay();
if (response.isValid() && response.statusCode == 200) { if (response.isValid() && response.statusCode == 200) {
@ -701,7 +700,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
) )
); );
if (InvenTreeAPI().supportModernBarcodes) { if (api.supportModernBarcodes) {
tiles.add( tiles.add(
customBarcodeActionTile(context, this, part.customBarcode, "part", part.pk) customBarcodeActionTile(context, this, part.customBarcode, "part", part.pk)
); );

View File

@ -1,6 +1,9 @@
import "package:flutter/material.dart";
import "package:inventree/api.dart";
import "package:inventree/widget/back.dart"; import "package:inventree/widget/back.dart";
import "package:inventree/widget/drawer.dart"; import "package:inventree/widget/drawer.dart";
import "package:flutter/material.dart";
/* /*
@ -62,6 +65,9 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> with
bool get loaded => !loading; bool get loaded => !loading;
// Helper function to return API instance
InvenTreeAPI get api => InvenTreeAPI();
// Update current tab selection // Update current tab selection
void onTabSelectionChanged(int index) { void onTabSelectionChanged(int index) {