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:
parent
c7527e8b4e
commit
9485d858eb
@ -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
|
||||||
---
|
---
|
||||||
|
|
||||||
|
19
lib/api.dart
19
lib/api.dart
@ -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 = {};
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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) {
|
||||||
|
@ -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,13 +81,14 @@ 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(
|
||||||
filters: {
|
filters: {
|
||||||
"supplier": widget.company.pk.toString()
|
"supplier": widget.company.pk.toString()
|
||||||
}
|
}
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
);
|
);
|
||||||
|
@ -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)
|
||||||
);
|
);
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user