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

Make notes widget "generic" (#327)

* Make notes widget "generic"

- No longer tied to the "part" model
- Will allow us to use it elsewhere

* Update release notes

* Add helper methods for checking model permissions

* Refactoring of permissions checks

* Add notes to the "purchase order" widget

* Fix typos

* remove bom tab from part view

* linting fixes
This commit is contained in:
Oliver 2023-04-19 21:57:28 +10:00 committed by GitHub
parent 28ed1ed545
commit b9ffabd561
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 192 additions and 168 deletions

View File

@ -4,6 +4,7 @@
- Fix background image transparency for dark mode - Fix background image transparency for dark mode
- Fix link to Bill of Materials from Part screen - Fix link to Bill of Materials from Part screen
- Improvements to supplier part detail screen - Improvements to supplier part detail screen
- Add "notes" field to more models
### 0.11.4 - April 2023 ### 0.11.4 - April 2023

View File

@ -650,16 +650,20 @@ class InvenTreeAPI {
* e.g. "part", "change" * e.g. "part", "change"
*/ */
bool checkPermission(String role, String permission) { bool checkPermission(String role, String permission) {
// If we do not have enough information, assume permission is allowed // If we do not have enough information, assume permission is allowed
if (roles.isEmpty) { if (roles.isEmpty) {
debug("checkPermission - no roles defined!");
return true; return true;
} }
if (!roles.containsKey(role)) { if (!roles.containsKey(role)) {
debug("checkPermission - role '$role' not found!");
return true; return true;
} }
if (roles[role] == null) { if (roles[role] == null) {
debug("checkPermission - role '$role' is null!");
return true; return true;
} }

View File

@ -18,6 +18,9 @@ class InvenTreeCompany extends InvenTreeModel {
@override @override
String get URL => "company/"; String get URL => "company/";
@override
List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"];
@override @override
Map<String, dynamic> formFields() { Map<String, dynamic> formFields() {
return { return {
@ -118,6 +121,9 @@ class InvenTreeSupplierPart extends InvenTreeModel {
@override @override
String get URL => "company/part/"; String get URL => "company/part/";
@override
List<String> get rolesRequired => ["part", "purchase_order"];
@override @override
Map<String, dynamic> formFields() { Map<String, dynamic> formFields() {
return { return {

View File

@ -10,6 +10,7 @@ import "package:inventree/api.dart";
import "package:inventree/api_form.dart"; import "package:inventree/api_form.dart";
import "package:inventree/fa_icon_mapping.dart"; import "package:inventree/fa_icon_mapping.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/helpers.dart";
import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/sentry.dart";
import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/dialogs.dart";
@ -78,7 +79,63 @@ class InvenTreeModel {
} else { } else {
return ""; return "";
} }
}
/* Return a list of roles which may be required for this model
* If multiple roles are required, *any* role which passes the check is sufficient
*/
List<String> get rolesRequired {
// Default implementation should not be called
debug("rolesRequired() not implemented for model ${URL} - returning empty list");
return [];
}
// Test if the user can "edit" this model
bool get canEdit {
for (String role in rolesRequired) {
if (InvenTreeAPI().checkPermission(role, "change")) {
return true;
}
}
// Fallback
return false;
}
// Test if the user can "create" this model
bool get canCreate {
for (String role in rolesRequired) {
if (InvenTreeAPI().checkPermission(role, "add")) {
return true;
}
}
// Fallback
return false;
}
// Test if the user can "delete" this model
bool get canDelete {
for (String role in rolesRequired) {
if (InvenTreeAPI().checkPermission(role, "delete")) {
return true;
}
}
// Fallback
return false;
}
// Test if the user can "view" this model
bool get canView {
for (String role in rolesRequired) {
if (InvenTreeAPI().checkPermission(role, "view")) {
return true;
}
}
// Fallback
return false;
} }
// Fields for editing / creating this model // Fields for editing / creating this model

View File

@ -23,6 +23,9 @@ class InvenTreePartCategory extends InvenTreeModel {
@override @override
String get URL => "part/category/"; String get URL => "part/category/";
@override
List<String> get rolesRequired => ["part_category"];
@override @override
Map<String, dynamic> formFields() { Map<String, dynamic> formFields() {
@ -182,6 +185,9 @@ class InvenTreePart extends InvenTreeModel {
@override @override
String get URL => "part/"; String get URL => "part/";
@override
List<String> get rolesRequired => ["part"];
@override @override
Map<String, dynamic> formFields() { Map<String, dynamic> formFields() {
return { return {

View File

@ -18,6 +18,9 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
@override @override
String get URL => "order/po/"; String get URL => "order/po/";
@override
List<String> get rolesRequired => ["purchase_order"];
String get receive_url => "${url}receive/"; String get receive_url => "${url}receive/";
@override @override

View File

@ -20,6 +20,9 @@ class InvenTreeStockItemTestResult extends InvenTreeModel {
@override @override
String get URL => "stock/test/"; String get URL => "stock/test/";
@override
List<String> get rolesRequired => ["stock"];
@override @override
Map<String, dynamic> formFields() { Map<String, dynamic> formFields() {
return { return {
@ -134,6 +137,9 @@ class InvenTreeStockItem extends InvenTreeModel {
@override @override
String get URL => "stock/"; String get URL => "stock/";
@override
List<String> get rolesRequired => ["stock"];
// URLs for performing stock actions // URLs for performing stock actions
static String transferStockUrl() => "stock/transfer/"; static String transferStockUrl() => "stock/transfer/";
@ -611,6 +617,9 @@ class InvenTreeStockLocation extends InvenTreeModel {
@override @override
String get URL => "stock/location/"; String get URL => "stock/location/";
@override
List<String> get rolesRequired => ["stock_location"];
String get pathstring => (jsondata["pathstring"] ?? "") as String; String get pathstring => (jsondata["pathstring"] ?? "") as String;
@override @override

View File

@ -40,7 +40,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
List<Widget> actions = []; List<Widget> actions = [];
if (widget.category != null) { if (widget.category != null) {
if (api.checkPermission("part_category", "change")) { if (InvenTreePartCategory().canEdit) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),
@ -60,7 +60,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
List<SpeedDialChild> actionButtons(BuildContext context) { List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("part", "add")) { if (InvenTreePart().canCreate) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.shapes), child: FaIcon(FontAwesomeIcons.shapes),
@ -70,7 +70,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
); );
} }
if (api.checkPermission("part_category", "add")) { if (InvenTreePartCategory().canCreate) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.sitemap), child: FaIcon(FontAwesomeIcons.sitemap),

View File

@ -49,9 +49,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
List<Widget> appBarActions(BuildContext context) { List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = []; List<Widget> actions = [];
if (api.checkPermission("purchase_order", "change") || if (InvenTreeCompany().canEdit) {
api.checkPermission("sales_order", "change") ||
api.checkPermission("return_order", "change")) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),
@ -281,7 +279,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
builder: (context) => AttachmentWidget( builder: (context) => AttachmentWidget(
InvenTreeCompanyAttachment(), InvenTreeCompanyAttachment(),
widget.company.pk, widget.company.pk,
api.checkPermission("purchase_order", "change") || api.checkPermission("sales_order", "change") InvenTreeCompany().canEdit
) )
) )
); );

View File

@ -3,6 +3,9 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/app_colors.dart"; import "package:inventree/app_colors.dart";
import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/settings/settings.dart"; import "package:inventree/settings/settings.dart";
import "package:inventree/widget/category_display.dart"; import "package:inventree/widget/category_display.dart";
@ -95,7 +98,7 @@ class InvenTreeDrawer extends StatelessWidget {
tiles.add(Divider()); tiles.add(Divider());
if (InvenTreeAPI().checkPermission("part_category", "view")) { if (InvenTreeCompany().canView) {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().parts), title: Text(L10().parts),
@ -105,7 +108,7 @@ class InvenTreeDrawer extends StatelessWidget {
); );
} }
if (InvenTreeAPI().checkPermission("stock_location", "view")) { if (InvenTreeStockLocation().canView) {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().stock), title: Text(L10().stock),
@ -115,7 +118,7 @@ class InvenTreeDrawer extends StatelessWidget {
); );
} }
if (InvenTreeAPI().checkPermission("purchase_order", "view")) { if (InvenTreePurchaseOrder().canView) {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().purchaseOrders), title: Text(L10().purchaseOrders),

View File

@ -63,7 +63,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
} }
// Add "edit" button // Add "edit" button
if (location != null && api.checkPermission("stock_location", "change")) { if (location != null && InvenTreeStockLocation().canEdit) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),
@ -85,7 +85,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
if (location != null) { if (location != null) {
// Scan items into this location // Scan items into this location
if (api.checkPermission("stock", "change")) { if (InvenTreeStockItem().canEdit) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.qrcode), child: FaIcon(FontAwesomeIcons.qrcode),
@ -105,7 +105,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
} }
// Scan this location into another one // Scan this location into another one
if (api.checkPermission("stock_location", "change")) { if (InvenTreeStockLocation().canEdit) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.qrcode), child: FaIcon(FontAwesomeIcons.qrcode),
@ -144,7 +144,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
// Create new location // Create new location
if (api.checkPermission("stock_location", "add")) { if (InvenTreeStockLocation().canCreate) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.sitemap), child: FaIcon(FontAwesomeIcons.sitemap),
@ -157,7 +157,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
} }
// Create new item // Create new item
if (location != null && api.checkPermission("stock", "add")) { if (location != null && InvenTreeStockItem().canCreate) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.boxesStacked), child: FaIcon(FontAwesomeIcons.boxesStacked),

View File

@ -1,49 +1,56 @@
import "package:flutter/material.dart"; 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/inventree/model.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:flutter_markdown/flutter_markdown.dart"; import "package:flutter_markdown/flutter_markdown.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
class PartNotesWidget extends StatefulWidget { /*
* A widget for displaying the notes associated with a given model.
* We need to pass in the following parameters:
*
* - Model instance
* - Title for the app bar
*/
class NotesWidget extends StatefulWidget {
const PartNotesWidget(this.part, {Key? key}) : super(key: key); const NotesWidget(this.model, {Key? key}) : super(key: key);
final InvenTreePart part; final InvenTreeModel model;
@override @override
_PartNotesState createState() => _PartNotesState(part); _NotesState createState() => _NotesState();
} }
class _PartNotesState extends RefreshableState<PartNotesWidget> { /*
* Class representing the state of the NotesWidget
*/
class _NotesState extends RefreshableState<NotesWidget> {
_PartNotesState(this.part); _NotesState();
final InvenTreePart part;
@override @override
Future<void> request(BuildContext context) async { Future<void> request(BuildContext context) async {
await part.reload(); await widget.model.reload();
} }
@override @override
String getAppBarTitle() => L10().partNotes; String getAppBarTitle() => L10().editNotes;
@override @override
List<Widget> appBarActions(BuildContext context) { List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = []; List<Widget> actions = [];
if (InvenTreeAPI().checkPermission("part", "change")) { if (widget.model.canEdit) {
actions.add( actions.add(
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.penToSquare), icon: FaIcon(FontAwesomeIcons.penToSquare),
tooltip: L10().edit, tooltip: L10().edit,
onPressed: () { onPressed: () {
part.editForm( widget.model.editForm(
context, context,
L10().editNotes, L10().editNotes,
fields: { fields: {
@ -67,7 +74,7 @@ class _PartNotesState extends RefreshableState<PartNotesWidget> {
Widget getBody(BuildContext context) { Widget getBody(BuildContext context) {
return Markdown( return Markdown(
selectable: false, selectable: false,
data: part.notes, data: widget.model.notes,
); );
} }

View File

@ -16,7 +16,7 @@ import "package:inventree/preferences.dart";
import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/attachment_widget.dart";
import "package:inventree/widget/bom_list.dart"; import "package:inventree/widget/bom_list.dart";
import "package:inventree/widget/part_list.dart"; import "package:inventree/widget/part_list.dart";
import "package:inventree/widget/part_notes.dart"; import "package:inventree/widget/notes_widget.dart";
import "package:inventree/widget/part_parameter_widget.dart"; import "package:inventree/widget/part_parameter_widget.dart";
import "package:inventree/widget/progress.dart"; import "package:inventree/widget/progress.dart";
import "package:inventree/widget/category_display.dart"; import "package:inventree/widget/category_display.dart";
@ -72,7 +72,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
List<Widget> appBarActions(BuildContext context) { List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = []; List<Widget> actions = [];
if (api.checkPermission("part", "change")) { if (InvenTreePart().canEdit) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),
@ -90,7 +90,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
List<SpeedDialChild> barcodeButtons(BuildContext context) { List<SpeedDialChild> barcodeButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("part", "change")) { if (InvenTreePart().canEdit) {
if (api.supportModernBarcodes) { if (api.supportModernBarcodes) {
actions.add( actions.add(
customBarcodeAction( customBarcodeAction(
@ -109,7 +109,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
List<SpeedDialChild> actionButtons(BuildContext context) { List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("stock", "add")) { if (InvenTreeStockItem().canCreate) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.box), child: FaIcon(FontAwesomeIcons.box),
@ -234,7 +234,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
*/ */
Future <void> _toggleStar(BuildContext context) async { Future <void> _toggleStar(BuildContext context) async {
if (api.checkPermission("part", "view")) { if (InvenTreePart().canView) {
showLoadingOverlay(context); showLoadingOverlay(context);
await part.update(values: {"starred": "${!part.starred}"}); await part.update(values: {"starred": "${!part.starred}"});
hideLoadingOverlay(); hideLoadingOverlay();
@ -557,7 +557,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => PartNotesWidget(part)) MaterialPageRoute(builder: (context) => NotesWidget(part))
); );
}, },
) )
@ -575,7 +575,8 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
builder: (context) => AttachmentWidget( builder: (context) => AttachmentWidget(
InvenTreePartAttachment(), InvenTreePartAttachment(),
part.pk, part.pk,
api.checkPermission("part", "change")) part.canEdit
)
) )
); );
}, },
@ -678,10 +679,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
Tab(text: L10().stock) Tab(text: L10().stock)
]; ];
if (showBom && part.isAssembly) {
icons.add(Tab(text: L10().bom));
}
if (showParameters) { if (showParameters) {
icons.add(Tab(text: L10().parameters)); icons.add(Tab(text: L10().parameters));
} }
@ -703,10 +700,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
PaginatedStockItemList({"part": part.pk.toString()}, true) PaginatedStockItemList({"part": part.pk.toString()}, true)
]; ];
if (showBom && part.isAssembly) {
tabs.add(PaginatedBomList({"part": part.pk.toString()}, showSearch: true, isParentPart: true));
}
if (showParameters) { if (showParameters) {
tabs.add(PaginatedParameterList({"part": part.pk.toString()}, true)); tabs.add(PaginatedParameterList({"part": part.pk.toString()}, true));
} }

View File

@ -42,7 +42,7 @@ class _PartImageState extends RefreshableState<PartImageWidget> {
List<Widget> actions = []; List<Widget> actions = [];
if (InvenTreeAPI().checkPermission("part", "change")) { if (part.canEdit) {
// File upload // File upload
actions.add( actions.add(

View File

@ -14,6 +14,7 @@ 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/attachment_widget.dart";
import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/company_detail.dart";
import "package:inventree/widget/notes_widget.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/stock_list.dart"; import "package:inventree/widget/stock_list.dart";
@ -49,7 +50,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
List<Widget> appBarActions(BuildContext context) { List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = []; List<Widget> actions = [];
if (InvenTreeAPI().checkPermission("purchase_order", "change")) { if (order.canEdit) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),
@ -68,7 +69,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
List<SpeedDialChild> actionButtons(BuildContext context) { List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("purchase_order", "add")) { if (order.canCreate) {
if (order.isPending) { if (order.isPending) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
@ -255,6 +256,22 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
)); ));
} }
// Notes tile
tiles.add(
ListTile(
title: Text(L10().notes),
leading: FaIcon(FontAwesomeIcons.noteSticky, color: COLOR_ACTION),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotesWidget(order)
)
);
},
)
);
// Attachments // Attachments
tiles.add( tiles.add(
ListTile( ListTile(
@ -268,7 +285,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
builder: (context) => AttachmentWidget( builder: (context) => AttachmentWidget(
InvenTreePurchaseOrderAttachment(), InvenTreePurchaseOrderAttachment(),
order.pk, order.pk,
InvenTreeAPI().checkPermission("purchase_order", "change")) order.canEdit
)
) )
); );
}, },

View File

@ -52,7 +52,7 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
List<SpeedDialChild> actionButtons(BuildContext context) { List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("purchase_order", "add")) { if (InvenTreePurchaseOrder().canCreate) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.circlePlus), child: FaIcon(FontAwesomeIcons.circlePlus),

View File

@ -207,29 +207,29 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
}; };
// Part search // Part search
if (api.checkPermission("part", "view")) { if (InvenTreePart().canView) {
body["part"] = {}; body["part"] = {};
} }
// PartCategory search // PartCategory search
if (api.checkPermission("part_category", "view")) { if (InvenTreePartCategory().canView) {
body["partcategory"] = {}; body["partcategory"] = {};
} }
// StockItem search // StockItem search
if (api.checkPermission("stock", "view")) { if (InvenTreeStockItem().canView) {
body["stockitem"] = { body["stockitem"] = {
"in_stock": true, "in_stock": true,
}; };
} }
// StockLocation search // StockLocation search
if (api.checkPermission("stock_location", "view")) { if (InvenTreeStockLocation().canView) {
body["stocklocation"] = {}; body["stocklocation"] = {};
} }
// PurchaseOrder search // PurchaseOrder search
if (api.checkPermission("purchase_order", "view")) { if (InvenTreePurchaseOrder().canView) {
body["purchaseorder"] = { body["purchaseorder"] = {
"outstanding": true "outstanding": true
}; };
@ -253,7 +253,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
Future<void> legacySearch(String term) async { Future<void> legacySearch(String term) async {
// Search parts // Search parts
if (api.checkPermission("part", "view")) { if (InvenTreePart().canView) {
nPendingSearches++; nPendingSearches++;
InvenTreePart().count(searchQuery: term).then((int n) { InvenTreePart().count(searchQuery: term).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
@ -268,7 +268,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
} }
// Search part categories // Search part categories
if (api.checkPermission("part_category", "view")) { if (InvenTreePartCategory().canView) {
nPendingSearches++; nPendingSearches++;
InvenTreePartCategory().count(searchQuery: term,).then((int n) { InvenTreePartCategory().count(searchQuery: term,).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
@ -283,7 +283,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
} }
// Search stock items // Search stock items
if (api.checkPermission("stock", "view")) { if (InvenTreeStockItem().canView) {
nPendingSearches++; nPendingSearches++;
InvenTreeStockItem().count(searchQuery: term).then((int n) { InvenTreeStockItem().count(searchQuery: term).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
@ -298,7 +298,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
} }
// Search stock locations // Search stock locations
if (api.checkPermission("stock_location", "view")) { if (InvenTreeStockLocation().canView) {
nPendingSearches++; nPendingSearches++;
InvenTreeStockLocation().count(searchQuery: term).then((int n) { InvenTreeStockLocation().count(searchQuery: term).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
@ -313,7 +313,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
} }
// Search purchase orders // Search purchase orders
if (api.checkPermission("purchase_order", "view")) { if (InvenTreePurchaseOrder().canView) {
nPendingSearches++; nPendingSearches++;
InvenTreePurchaseOrder().count( InvenTreePurchaseOrder().count(
searchQuery: term, searchQuery: term,

View File

@ -25,7 +25,7 @@ import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/stock_item_history.dart"; import "package:inventree/widget/stock_item_history.dart";
import "package:inventree/widget/stock_item_test_results.dart"; import "package:inventree/widget/stock_item_test_results.dart";
import "package:inventree/widget/stock_notes.dart"; import "package:inventree/widget/notes_widget.dart";
class StockDetailWidget extends StatefulWidget { class StockDetailWidget extends StatefulWidget {
@ -64,7 +64,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
); );
} }
if (api.checkPermission("stock", "change")) { if (widget.item.canEdit) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),
@ -84,7 +84,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("stock", "change")) { if (widget.item.canEdit) {
// Stock adjustment actions available if item is *not* serialized // Stock adjustment actions available if item is *not* serialized
if (!widget.item.isSerialized()) { if (!widget.item.isSerialized()) {
@ -138,7 +138,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
); );
} }
if (api.checkPermission("stock", "delete")) { if (widget.item.canDelete) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.trashCan, color: Colors.red), child: FaIcon(FontAwesomeIcons.trashCan, color: Colors.red),
@ -157,7 +157,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
List<SpeedDialChild> barcodeButtons(BuildContext context) { List<SpeedDialChild> barcodeButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("stock", "change")) { if (widget.item.canEdit) {
// Scan item into location // Scan item into location
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
@ -816,7 +816,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => StockNotesWidget(widget.item)) MaterialPageRoute(builder: (context) => NotesWidget(widget.item))
); );
} }
) )
@ -834,7 +834,8 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
builder: (context) => AttachmentWidget( builder: (context) => AttachmentWidget(
InvenTreeStockItemAttachment(), InvenTreeStockItemAttachment(),
widget.item.pk, widget.item.pk,
InvenTreeAPI().checkPermission("stock", "change")) widget.item.canEdit,
)
) )
); );
}, },

View File

@ -1,77 +0,0 @@
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:flutter_markdown/flutter_markdown.dart";
import "package:inventree/l10.dart";
import "package:inventree/api.dart";
class StockNotesWidget extends StatefulWidget {
const StockNotesWidget(this.item, {Key? key}) : super(key: key);
final InvenTreeStockItem item;
@override
_StockNotesState createState() => _StockNotesState(item);
}
class _StockNotesState extends RefreshableState<StockNotesWidget> {
_StockNotesState(this.item);
final InvenTreeStockItem item;
@override
String getAppBarTitle() => L10().stockItemNotes;
@override
Future<void> request(BuildContext context) async {
if (item.pk > 0) {
await item.reload();
}
}
@override
List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = [];
if (InvenTreeAPI().checkPermission("stock", "change")) {
actions.add(
IconButton(
icon: FaIcon(FontAwesomeIcons.penToSquare),
tooltip: L10().edit,
onPressed: () {
item.editForm(
context,
L10().editNotes,
fields: {
"notes": {
"multiline": true,
}
},
onSuccess: (data) async {
refresh(context);
}
);
}
)
);
}
return actions;
}
@override
Widget getBody(BuildContext context) {
return Markdown(
selectable: false,
data: item.notes,
);
}
}

View File

@ -57,10 +57,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
List<SpeedDialChild> barcodeButtons(BuildContext context) { List<SpeedDialChild> barcodeButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (api.checkPermission("purchase_order", "change") || if (widget.supplierPart.canEdit) {
api.checkPermission("sales_order", "change") ||
api.checkPermission("return_order", "change")) {
actions.add( actions.add(
customBarcodeAction( customBarcodeAction(
context, this, context, this,
@ -78,9 +75,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
List<Widget> appBarActions(BuildContext context) { List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = []; List<Widget> actions = [];
if (api.checkPermission("purchase_order", "change") || if (widget.supplierPart.canEdit) {
api.checkPermission("sales_order", "change") ||
api.checkPermission("return_order", "change")) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),