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:
parent
28ed1ed545
commit
b9ffabd561
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
@ -49,17 +49,15 @@ 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),
|
||||||
tooltip: L10().companyEdit,
|
tooltip: L10().companyEdit,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
editCompany(context);
|
editCompany(context);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -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),
|
||||||
|
@ -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),
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
@ -263,13 +280,14 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
|||||||
trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
|
trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => AttachmentWidget(
|
builder: (context) => AttachmentWidget(
|
||||||
InvenTreePurchaseOrderAttachment(),
|
InvenTreePurchaseOrderAttachment(),
|
||||||
order.pk,
|
order.pk,
|
||||||
InvenTreeAPI().checkPermission("purchase_order", "change"))
|
order.canEdit
|
||||||
)
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -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),
|
||||||
|
@ -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,
|
||||||
|
@ -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))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -829,13 +829,14 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
|
trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => AttachmentWidget(
|
builder: (context) => AttachmentWidget(
|
||||||
InvenTreeStockItemAttachment(),
|
InvenTreeStockItemAttachment(),
|
||||||
widget.item.pk,
|
widget.item.pk,
|
||||||
InvenTreeAPI().checkPermission("stock", "change"))
|
widget.item.canEdit,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user