diff --git a/backend/patches/createInventoryNumberSeries.ts b/backend/patches/createInventoryNumberSeries.ts new file mode 100644 index 00000000..4b4e6c56 --- /dev/null +++ b/backend/patches/createInventoryNumberSeries.ts @@ -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 }; diff --git a/backend/patches/index.ts b/backend/patches/index.ts index 796bff8c..9dee78bb 100644 --- a/backend/patches/index.ts +++ b/backend/patches/index.ts @@ -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[]; diff --git a/fyo/utils/format.ts b/fyo/utils/format.ts index f89cc18a..74a5a564 100644 --- a/fyo/utils/format.ts +++ b/fyo/utils/format.ts @@ -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(); } diff --git a/models/baseModels/Item/Item.ts b/models/baseModels/Item/Item.ts index 7ad8a6d1..83c246c9 100644 --- a/models/baseModels/Item/Item.ts +++ b/models/baseModels/Item/Item.ts @@ -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', + }; } diff --git a/models/index.ts b/models/index.ts index 36e50235..6d9a3f79 100644 --- a/models/index.ts +++ b/models/index.ts @@ -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( diff --git a/models/inventory/Location.ts b/models/inventory/Location.ts new file mode 100644 index 00000000..eb84bbb9 --- /dev/null +++ b/models/inventory/Location.ts @@ -0,0 +1,5 @@ +import { Doc } from 'fyo/model/doc'; + +export class Location extends Doc { + item?: string; +} diff --git a/models/inventory/StockLedgerEntry.ts b/models/inventory/StockLedgerEntry.ts new file mode 100644 index 00000000..42a9d5aa --- /dev/null +++ b/models/inventory/StockLedgerEntry.ts @@ -0,0 +1,3 @@ +import { Doc } from 'fyo/model/doc'; + +export class StockLedgerEntry extends Doc {} diff --git a/models/inventory/StockMovement.ts b/models/inventory/StockMovement.ts new file mode 100644 index 00000000..e0ec5da1 --- /dev/null +++ b/models/inventory/StockMovement.ts @@ -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'] }; + } +} diff --git a/models/inventory/StockMovementItem.ts b/models/inventory/StockMovementItem.ts new file mode 100644 index 00000000..eb3047cc --- /dev/null +++ b/models/inventory/StockMovementItem.ts @@ -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, + }; +} diff --git a/models/inventory/StockQueue.ts b/models/inventory/StockQueue.ts new file mode 100644 index 00000000..b0ade7bb --- /dev/null +++ b/models/inventory/StockQueue.ts @@ -0,0 +1,3 @@ +import { Doc } from 'fyo/model/doc'; + +export class StockQueue extends Doc {} \ No newline at end of file diff --git a/models/inventory/types.ts b/models/inventory/types.ts new file mode 100644 index 00000000..bb125e90 --- /dev/null +++ b/models/inventory/types.ts @@ -0,0 +1,4 @@ +export type MovementType = + | 'MaterialIssue' + | 'MaterialReceipt' + | 'MaterialTransfer'; diff --git a/models/types.ts b/models/types.ts index 476355e6..a8fb4c1c 100644 --- a/models/types.ts +++ b/models/types.ts @@ -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; diff --git a/schemas/app/Item.json b/schemas/app/Item.json index 3d30d770..152eb6a0 100644 --- a/schemas/app/Item.json +++ b/schemas/app/Item.json @@ -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"] } diff --git a/schemas/app/NumberSeries.json b/schemas/app/NumberSeries.json index 95847b25..00be44aa 100644 --- a/schemas/app/NumberSeries.json +++ b/schemas/app/NumberSeries.json @@ -46,6 +46,10 @@ { "value": "JournalEntry", "label": "Journal Entry" + }, + { + "value": "StockMovement", + "label": "Stock Movement" } ], "default": "-", diff --git a/schemas/app/inventory/Location.json b/schemas/app/inventory/Location.json new file mode 100644 index 00000000..d3693faf --- /dev/null +++ b/schemas/app/inventory/Location.json @@ -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" + ] +} diff --git a/schemas/app/inventory/StockLedgerEntry.json b/schemas/app/inventory/StockLedgerEntry.json new file mode 100644 index 00000000..5b1a6045 --- /dev/null +++ b/schemas/app/inventory/StockLedgerEntry.json @@ -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 + } + ] +} diff --git a/schemas/app/inventory/StockMovement.json b/schemas/app/inventory/StockMovement.json new file mode 100644 index 00000000..1632c925 --- /dev/null +++ b/schemas/app/inventory/StockMovement.json @@ -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"] +} diff --git a/schemas/app/inventory/StockMovementItem.json b/schemas/app/inventory/StockMovementItem.json new file mode 100644 index 00000000..2ba7a6a3 --- /dev/null +++ b/schemas/app/inventory/StockMovementItem.json @@ -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" + ] +} diff --git a/schemas/app/inventory/StockQueue.json b/schemas/app/inventory/StockQueue.json new file mode 100644 index 00000000..87962718 --- /dev/null +++ b/schemas/app/inventory/StockQueue.json @@ -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 + } + ] +} diff --git a/schemas/schemas.ts b/schemas/schemas.ts index 0d8cb044..40169006 100644 --- a/schemas/schemas.ts +++ b/schemas/schemas.ts @@ -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, ]; diff --git a/src/components/Controls/FormControl.vue b/src/components/Controls/FormControl.vue index 2c01dd8b..02230c01 100644 --- a/src/components/Controls/FormControl.vue +++ b/src/components/Controls/FormControl.vue @@ -24,6 +24,7 @@ const components = { Select, Link, Date, + Datetime: Date, Table, AutoComplete, DynamicLink, diff --git a/src/components/Controls/Table.vue b/src/components/Controls/Table.vue index fa2e91bc..34d8c497 100644 --- a/src/components/Controls/Table.vue +++ b/src/components/Controls/Table.vue @@ -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)); }, }, diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 120c0782..db4a424d 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -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(() => { diff --git a/src/utils/sidebarConfig.ts b/src/utils/sidebarConfig.ts index f7f88ae8..ffabe7a2 100644 --- a/src/utils/sidebarConfig.ts +++ b/src/utils/sidebarConfig.ts @@ -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 { + 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 { + 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 { return [ { label: t`Get Started`, @@ -200,8 +222,8 @@ function getCompleteSidebar(): SidebarConfig { }, ], }, - getInventorySidebar(), - getRegionalSidebar(), + await getInventorySidebar(), + await getRegionalSidebar(), { label: t`Setup`, name: 'setup',