mirror of
https://github.com/frappe/books.git
synced 2025-01-03 07:12:21 +00:00
incr: add enable inventory
- add basic inventory schemas - add basic inventory models - patch stockmovement number series
This commit is contained in:
parent
afcc5fb2b2
commit
2c0540bfd5
22
backend/patches/createInventoryNumberSeries.ts
Normal file
22
backend/patches/createInventoryNumberSeries.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { getDefaultMetaFieldValueMap } from '../../backend/helpers';
|
||||
import { DatabaseManager } from '../database/manager';
|
||||
|
||||
async function execute(dm: DatabaseManager) {
|
||||
const schemaName = 'NumberSeries';
|
||||
const name = 'SMOV-';
|
||||
const exists = await dm.db?.exists(schemaName, name);
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
await dm.db?.insert(schemaName, {
|
||||
name,
|
||||
start: 1001,
|
||||
padZeros: 4,
|
||||
current: 0,
|
||||
referenceType: 'StockMovement',
|
||||
...getDefaultMetaFieldValueMap(),
|
||||
});
|
||||
}
|
||||
|
||||
export default { execute, beforeMigrate: true };
|
@ -1,5 +1,6 @@
|
||||
import { Patch } from '../database/types';
|
||||
import addUOMs from './addUOMs';
|
||||
import createInventoryNumberSeries from './createInventoryNumberSeries';
|
||||
import fixRoundOffAccount from './fixRoundOffAccount';
|
||||
import testPatch from './testPatch';
|
||||
import updateSchemas from './updateSchemas';
|
||||
@ -20,6 +21,11 @@ export default [
|
||||
{
|
||||
name: 'fixRoundOffAccount',
|
||||
version: '0.6.3-beta.0',
|
||||
patch: fixRoundOffAccount
|
||||
patch: fixRoundOffAccount,
|
||||
},
|
||||
{
|
||||
name: 'createInventoryNumberSeries',
|
||||
version: '0.6.6-beta.0',
|
||||
patch: createInventoryNumberSeries,
|
||||
},
|
||||
] as Patch[];
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
DEFAULT_CURRENCY,
|
||||
DEFAULT_DATE_FORMAT,
|
||||
DEFAULT_DISPLAY_PRECISION,
|
||||
DEFAULT_LOCALE,
|
||||
DEFAULT_LOCALE
|
||||
} from './consts';
|
||||
|
||||
export function format(
|
||||
@ -32,6 +32,10 @@ export function format(
|
||||
return formatDate(value, fyo);
|
||||
}
|
||||
|
||||
if (field.fieldtype === FieldTypeEnum.Datetime) {
|
||||
return formatDate(value, fyo);
|
||||
}
|
||||
|
||||
if (field.fieldtype === FieldTypeEnum.Check) {
|
||||
return Boolean(value).toString();
|
||||
}
|
||||
|
@ -5,14 +5,17 @@ import {
|
||||
Action,
|
||||
FiltersMap,
|
||||
FormulaMap,
|
||||
HiddenMap,
|
||||
ListViewSettings,
|
||||
ValidationMap,
|
||||
ValidationMap
|
||||
} from 'fyo/model/types';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { Money } from 'pesa';
|
||||
import { AccountRootTypeEnum, AccountTypeEnum } from '../Account/types';
|
||||
|
||||
export class Item extends Doc {
|
||||
itemType?: 'Product' | 'Service';
|
||||
|
||||
formulas: FormulaMap = {
|
||||
incomeAccount: {
|
||||
formula: async () => {
|
||||
@ -99,4 +102,8 @@ export class Item extends Doc {
|
||||
columns: ['name', 'unit', 'tax', 'rate'],
|
||||
};
|
||||
}
|
||||
|
||||
hidden: HiddenMap = {
|
||||
trackItem: () => this.itemType !== 'Product',
|
||||
};
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem
|
||||
import { SetupWizard } from './baseModels/SetupWizard/SetupWizard';
|
||||
import { Tax } from './baseModels/Tax/Tax';
|
||||
import { TaxSummary } from './baseModels/TaxSummary/TaxSummary';
|
||||
import { Location } from './inventory/Location';
|
||||
import { StockLedgerEntry } from './inventory/StockLedgerEntry';
|
||||
import { StockMovement } from './inventory/StockMovement';
|
||||
import { StockMovementItem } from './inventory/StockMovementItem';
|
||||
import { StockQueue } from './inventory/StockQueue';
|
||||
|
||||
export const models = {
|
||||
Account,
|
||||
@ -37,6 +42,12 @@ export const models = {
|
||||
SetupWizard,
|
||||
Tax,
|
||||
TaxSummary,
|
||||
// Inventory Models
|
||||
StockQueue,
|
||||
StockMovement,
|
||||
StockMovementItem,
|
||||
StockLedgerEntry,
|
||||
Location,
|
||||
} as ModelMap;
|
||||
|
||||
export async function getRegionalModels(
|
||||
|
5
models/inventory/Location.ts
Normal file
5
models/inventory/Location.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
|
||||
export class Location extends Doc {
|
||||
item?: string;
|
||||
}
|
3
models/inventory/StockLedgerEntry.ts
Normal file
3
models/inventory/StockLedgerEntry.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
|
||||
export class StockLedgerEntry extends Doc {}
|
27
models/inventory/StockMovement.ts
Normal file
27
models/inventory/StockMovement.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { DefaultMap, FiltersMap, ListViewSettings } from 'fyo/model/types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
import { StockMovementItem } from './StockMovementItem';
|
||||
import { MovementType } from './types';
|
||||
|
||||
export class StockMovement extends Doc {
|
||||
name?: string;
|
||||
date?: Date;
|
||||
numberSeries?: string;
|
||||
movementType?: MovementType;
|
||||
items?: StockMovementItem;
|
||||
amount?: Money;
|
||||
|
||||
static filters: FiltersMap = {
|
||||
numberSeries: () => ({ referenceType: ModelNameEnum.StockMovement }),
|
||||
};
|
||||
|
||||
static defaults: DefaultMap = {
|
||||
date: () => new Date(),
|
||||
};
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return { columns: ['name', 'date', 'movementType'] };
|
||||
}
|
||||
}
|
29
models/inventory/StockMovementItem.ts
Normal file
29
models/inventory/StockMovementItem.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { FilterFunction, FiltersMap } from 'fyo/model/types';
|
||||
import { Money } from 'pesa';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
|
||||
const locationFilter: FilterFunction = (doc: Doc) => {
|
||||
const item = doc.item;
|
||||
if (!doc.item) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { item } as QueryFilter;
|
||||
};
|
||||
|
||||
export class StockMovementItem extends Doc {
|
||||
name?: string;
|
||||
item?: string;
|
||||
fromLocation?: string;
|
||||
toLocation?: string;
|
||||
quantity?: number;
|
||||
rate?: Money;
|
||||
amount?: Money;
|
||||
|
||||
static filters: FiltersMap = {
|
||||
item: () => ({ trackItem: true }),
|
||||
toLocation: locationFilter,
|
||||
fromLocation: locationFilter,
|
||||
};
|
||||
}
|
3
models/inventory/StockQueue.ts
Normal file
3
models/inventory/StockQueue.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
|
||||
export class StockQueue extends Doc {}
|
4
models/inventory/types.ts
Normal file
4
models/inventory/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type MovementType =
|
||||
| 'MaterialIssue'
|
||||
| 'MaterialReceipt'
|
||||
| 'MaterialTransfer';
|
@ -30,6 +30,11 @@ export enum ModelNameEnum {
|
||||
PatchRun = 'PatchRun',
|
||||
SingleValue = 'SingleValue',
|
||||
SystemSettings = 'SystemSettings',
|
||||
StockMovement = 'StockMovement',
|
||||
StockQueue = 'StockQueue',
|
||||
StockMovementItem = 'StockMovementItem',
|
||||
StockLedgerEntry = 'StockLedgerEntry',
|
||||
Location = 'Location',
|
||||
}
|
||||
|
||||
export type ModelName = keyof typeof ModelNameEnum;
|
||||
|
@ -105,6 +105,12 @@
|
||||
"label": "HSN/SAC",
|
||||
"fieldtype": "Int",
|
||||
"placeholder": "HSN/SAC Code"
|
||||
},
|
||||
{
|
||||
"fieldname": "trackItem",
|
||||
"label": "Track Item",
|
||||
"fieldtype": "Check",
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
"quickEditFields": [
|
||||
@ -116,7 +122,8 @@
|
||||
"description",
|
||||
"incomeAccount",
|
||||
"expenseAccount",
|
||||
"hsnCode"
|
||||
"hsnCode",
|
||||
"trackItem"
|
||||
],
|
||||
"keywordFields": ["name", "itemType", "for"]
|
||||
}
|
||||
|
@ -46,6 +46,10 @@
|
||||
{
|
||||
"value": "JournalEntry",
|
||||
"label": "Journal Entry"
|
||||
},
|
||||
{
|
||||
"value": "StockMovement",
|
||||
"label": "Stock Movement"
|
||||
}
|
||||
],
|
||||
"default": "-",
|
||||
|
25
schemas/app/inventory/Location.json
Normal file
25
schemas/app/inventory/Location.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Location",
|
||||
"label": "Location",
|
||||
"isSingle": false,
|
||||
"isChild": false,
|
||||
"naming": "manual",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "name",
|
||||
"label": "Location Name",
|
||||
"fieldtype": "Data",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "item",
|
||||
"label": "Item",
|
||||
"fieldtype": "Link",
|
||||
"target": "Item",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"quickEditFields": [
|
||||
"item"
|
||||
]
|
||||
}
|
86
schemas/app/inventory/StockLedgerEntry.json
Normal file
86
schemas/app/inventory/StockLedgerEntry.json
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"name": "StockLedgerEntry",
|
||||
"label": "Stock Ledger Entry",
|
||||
"isSingle": false,
|
||||
"isChild": false,
|
||||
"naming": "autoincrement",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "date",
|
||||
"label": "Date",
|
||||
"fieldtype": "Datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "item",
|
||||
"label": "Item",
|
||||
"fieldtype": "Link",
|
||||
"target": "Item",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"label": "Rate",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity",
|
||||
"label": "Quantity",
|
||||
"fieldtype": "Float",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "fromLocation",
|
||||
"label": "From Location",
|
||||
"fieldtype": "Link",
|
||||
"target": "Location",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "toLocation",
|
||||
"label": "To Location",
|
||||
"fieldtype": "Link",
|
||||
"target": "Location",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceDetailName",
|
||||
"label": "Ref. Detail Name",
|
||||
"fieldtype": "DynamicLink",
|
||||
"references": "referenceDetailType",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceDetailType",
|
||||
"label": "Ref. Detail Type",
|
||||
"fieldtype": "Data",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceName",
|
||||
"label": "Ref. Name",
|
||||
"fieldtype": "DynamicLink",
|
||||
"references": "referenceType",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceType",
|
||||
"label": "Ref. Type",
|
||||
"fieldtype": "Data",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "stockValue",
|
||||
"label": "Stock Value",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "stockValueDifference",
|
||||
"label": "Stock Value Difference",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
}
|
||||
]
|
||||
}
|
73
schemas/app/inventory/StockMovement.json
Normal file
73
schemas/app/inventory/StockMovement.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "StockMovement",
|
||||
"label": "Stock Movement",
|
||||
"naming": "numberSeries",
|
||||
"isSingle": false,
|
||||
"isChild": false,
|
||||
"isSubmittable": true,
|
||||
"fields": [
|
||||
{
|
||||
"label": "Stock Movement No.",
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"label": "Posting Date",
|
||||
"fieldtype": "Datetime",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "movementType",
|
||||
"label": "Movement Type",
|
||||
"fieldtype": "Select",
|
||||
"options": [
|
||||
{
|
||||
"value": "MaterialIssue",
|
||||
"label": "Material Issue"
|
||||
},
|
||||
{
|
||||
"value": "MaterialReceipt",
|
||||
"label": "Material Receipt"
|
||||
},
|
||||
{
|
||||
"value": "MaterialTransfer",
|
||||
"label": "Material Transfer"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "SMOV-"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"label": "Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
"fieldtype": "Table",
|
||||
"target": "StockMovementItem",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"quickEditFields": [
|
||||
"numberSeries",
|
||||
"date",
|
||||
"movementType",
|
||||
"amount",
|
||||
"items"
|
||||
],
|
||||
"keywordFields": ["name", "movementType"]
|
||||
}
|
59
schemas/app/inventory/StockMovementItem.json
Normal file
59
schemas/app/inventory/StockMovementItem.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "StockMovementItem",
|
||||
"label": "Stock Movement Item",
|
||||
"naming": "random",
|
||||
"isChild": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "item",
|
||||
"label": "Item",
|
||||
"fieldtype": "Link",
|
||||
"target": "Item",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "fromLocation",
|
||||
"label": "From",
|
||||
"fieldtype": "Link",
|
||||
"target": "Location",
|
||||
"create": true
|
||||
},
|
||||
{
|
||||
"fieldname": "toLocation",
|
||||
"label": "To",
|
||||
"fieldtype": "Link",
|
||||
"target": "Location",
|
||||
"create": true
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity",
|
||||
"label": "Quantity",
|
||||
"fieldtype": "Float",
|
||||
"required": true,
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"label": "Rate",
|
||||
"fieldtype": "Currency",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"label": "Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"tableFields": ["item", "fromLocation", "toLocation", "quantity", "rate"],
|
||||
"keywordFields": ["item"],
|
||||
"quickEditFields": [
|
||||
"item",
|
||||
"fromLocation",
|
||||
"toLocation",
|
||||
"quantity",
|
||||
"rate",
|
||||
"amount"
|
||||
]
|
||||
}
|
41
schemas/app/inventory/StockQueue.json
Normal file
41
schemas/app/inventory/StockQueue.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "StockQueue",
|
||||
"label": "Stock Queue",
|
||||
"isSingle": false,
|
||||
"isChild": false,
|
||||
"naming": "random",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "item",
|
||||
"label": "Item",
|
||||
"fieldtype": "Link",
|
||||
"target": "Item",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "location",
|
||||
"label": "Location",
|
||||
"fieldtype": "Link",
|
||||
"target": "Location",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "queue",
|
||||
"label": "Queue",
|
||||
"fieldtype": "Data",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "stockValue",
|
||||
"label": "Stock Value",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "valuationRate",
|
||||
"label": "Valuation Rate",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
}
|
||||
]
|
||||
}
|
@ -7,6 +7,11 @@ import CompanySettings from './app/CompanySettings.json';
|
||||
import Currency from './app/Currency.json';
|
||||
import Defaults from './app/Defaults.json';
|
||||
import GetStarted from './app/GetStarted.json';
|
||||
import Location from './app/inventory/Location.json';
|
||||
import StockLedgerEntry from './app/inventory/StockLedgerEntry.json';
|
||||
import StockMovement from './app/inventory/StockMovement.json';
|
||||
import StockMovementItem from './app/inventory/StockMovementItem.json';
|
||||
import StockQueue from './app/inventory/StockQueue.json';
|
||||
import Invoice from './app/Invoice.json';
|
||||
import InvoiceItem from './app/InvoiceItem.json';
|
||||
import Item from './app/Item.json';
|
||||
@ -88,4 +93,10 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
||||
Tax as Schema,
|
||||
TaxDetail as Schema,
|
||||
TaxSummary as Schema,
|
||||
|
||||
Location as Schema,
|
||||
StockQueue as Schema,
|
||||
StockLedgerEntry as Schema,
|
||||
StockMovement as Schema,
|
||||
StockMovementItem as Schema,
|
||||
];
|
||||
|
@ -24,6 +24,7 @@ const components = {
|
||||
Select,
|
||||
Link,
|
||||
Date,
|
||||
Datetime: Date,
|
||||
Table,
|
||||
AutoComplete,
|
||||
DynamicLink,
|
||||
|
@ -185,6 +185,7 @@ export default {
|
||||
},
|
||||
tableFields() {
|
||||
const fields = fyo.schemaMap[this.df.target].tableFields ?? [];
|
||||
console.log(this.df, fields);
|
||||
return fields.map((fieldname) => fyo.getField(this.df.target, fieldname));
|
||||
},
|
||||
},
|
||||
|
@ -167,7 +167,7 @@ export default {
|
||||
async mounted() {
|
||||
const { companyName } = await fyo.doc.getDoc('AccountingSettings');
|
||||
this.companyName = companyName;
|
||||
this.groups = getSidebarConfig();
|
||||
this.groups = await getSidebarConfig();
|
||||
|
||||
this.setActiveGroup();
|
||||
router.afterEach(() => {
|
||||
|
@ -1,12 +1,21 @@
|
||||
import { t } from 'fyo';
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { fyo } from '../initFyo';
|
||||
import { SidebarConfig, SidebarRoot } from './types';
|
||||
|
||||
export function getSidebarConfig(): SidebarConfig {
|
||||
const sideBar = getCompleteSidebar();
|
||||
export async function getSidebarConfig(): Promise<SidebarConfig> {
|
||||
const sideBar = await getCompleteSidebar();
|
||||
return getFilteredSidebar(sideBar);
|
||||
}
|
||||
|
||||
async function getIsInventoryEnabled(fyo: Fyo) {
|
||||
const values = await fyo.db.getAllRaw('Item', {
|
||||
fields: ['name'],
|
||||
filters: { trackItem: true },
|
||||
});
|
||||
|
||||
return !!values.length;
|
||||
}
|
||||
|
||||
function getFilteredSidebar(sideBar: SidebarConfig): SidebarConfig {
|
||||
return sideBar.filter((root) => {
|
||||
root.items = root.items?.filter((item) => {
|
||||
@ -53,19 +62,32 @@ function getRegionalSidebar(): SidebarRoot[] {
|
||||
];
|
||||
}
|
||||
|
||||
function getInventorySidebar(): SidebarRoot[] {
|
||||
async function getInventorySidebar(): Promise<SidebarRoot[]> {
|
||||
const showInventory = await getIsInventoryEnabled(fyo);
|
||||
if (!showInventory) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: t`Inventory`,
|
||||
name: 'inventory',
|
||||
icon: 'inventory',
|
||||
iconSize: '18',
|
||||
route: '/',
|
||||
route: '/list/StockMovement',
|
||||
items: [
|
||||
{
|
||||
label: t`Stock Movement`,
|
||||
name: 'stock-movement',
|
||||
route: '/list/StockMovement',
|
||||
schemaName: 'StockMovement',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getCompleteSidebar(): SidebarConfig {
|
||||
async function getCompleteSidebar(): Promise<SidebarConfig> {
|
||||
return [
|
||||
{
|
||||
label: t`Get Started`,
|
||||
@ -200,8 +222,8 @@ function getCompleteSidebar(): SidebarConfig {
|
||||
},
|
||||
],
|
||||
},
|
||||
getInventorySidebar(),
|
||||
getRegionalSidebar(),
|
||||
await getInventorySidebar(),
|
||||
await getRegionalSidebar(),
|
||||
{
|
||||
label: t`Setup`,
|
||||
name: 'setup',
|
||||
|
Loading…
Reference in New Issue
Block a user