diff --git a/fyo/model/doc.ts b/fyo/model/doc.ts index 14965ceb..5345e568 100644 --- a/fyo/model/doc.ts +++ b/fyo/model/doc.ts @@ -46,6 +46,7 @@ import { import { validateOptions, validateRequired } from './validationFunction'; import { getShouldDocSyncToERPNext } from 'src/utils/erpnextSync'; import { ModelNameEnum } from 'models/types'; +import { DocItem } from 'models/inventory/types'; export class Doc extends Observable { /* eslint-disable @typescript-eslint/no-floating-promises */ @@ -920,6 +921,25 @@ export class Doc extends Observable { return this; } + async _hasERPSyncableItems(): Promise { + const isSalesInvoice = this.schemaName === ModelNameEnum.SalesInvoice; + if (!isSalesInvoice) { + return true; + } + for (const item of this.items as DocItem[]) { + if (!item.item) { + continue; + } + const isFromERP = await this.fyo.getValue( + ModelNameEnum.Item, + item.item, + 'datafromErp' + ); + if (isFromERP) continue; + else return false; + } + return true; + } async sync(): Promise { this._syncing = true; @@ -936,10 +956,12 @@ export class Doc extends Observable { if (this._addDocToSyncQueue && !!this.shouldDocSyncToERPNext) { const isSalesInvoice = this.schemaName === ModelNameEnum.SalesInvoice; + const hasERPSyncableItems = await this._hasERPSyncableItems(); if ( - !(isSalesInvoice && this.isSyncedWithErp) || - (isSalesInvoice && !!this.isReturn) + hasERPSyncableItems && + (!(isSalesInvoice && this.isSyncedWithErp) || + (isSalesInvoice && !!this.isReturn)) ) { if (isSalesInvoice && !this.isReturn) { await this.setAndSync('isSyncedWithErp', true); diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts index 590702d2..b089ee41 100644 --- a/models/baseModels/InvoiceItem/InvoiceItem.ts +++ b/models/baseModels/InvoiceItem/InvoiceItem.ts @@ -19,8 +19,13 @@ import { Item } from '../Item/Item'; import { StockTransfer } from 'models/inventory/StockTransfer'; import { isPesa } from 'fyo/utils'; import { PricingRule } from '../PricingRule/PricingRule'; -import { getItemRateFromPriceList, getPricingRule } from 'models/helpers'; +import { + getItemRateFromPriceList, + getPricingRule, + getItemVisibility, +} from 'models/helpers'; import { SalesInvoice } from '../SalesInvoice/SalesInvoice'; +import { QueryFilter } from 'utils/db/types'; export abstract class InvoiceItem extends Doc { item?: string; @@ -646,13 +651,33 @@ export abstract class InvoiceItem extends Doc { }; static filters: FiltersMap = { - item: (doc: Doc) => { + item: async (doc: Doc): Promise => { let itemNotFor = 'Sales'; if (doc.isSales) { itemNotFor = 'Purchases'; } - return { for: ['not in', [itemNotFor]] }; + const filters: QueryFilter = { + for: ['not in', [itemNotFor]], + }; + + const enableERPNextSync = + doc.fyo.singles.AccountingSettings?.enableERPNextSync; + + if (enableERPNextSync) { + const itemVisibility = await getItemVisibility(doc.fyo); + + if (itemVisibility === 'Inventory Items') { + filters.trackItem = true; + } else if (itemVisibility === 'ERP Sync Items') { + filters.datafromErp = true; + } else if (itemVisibility === 'Non-Inventory Items') { + filters.trackItem = false; + filters.datafromErp = false; + } + } + + return filters; }, batch: async (doc: Doc) => { const batches = await doc.fyo.db.getAll(ModelNameEnum.Batch, { diff --git a/models/baseModels/Item/Item.ts b/models/baseModels/Item/Item.ts index 9971a1ba..1d0542b4 100644 --- a/models/baseModels/Item/Item.ts +++ b/models/baseModels/Item/Item.ts @@ -30,6 +30,7 @@ export class Item extends Doc { hsnCode?: number; hasSerialNumber?: boolean; serialNumberSeries?: string; + datafromErp?: boolean; uomConversions: UOMConversionItem[] = []; formulas: FormulaMap = { diff --git a/models/helpers.ts b/models/helpers.ts index 07e13435..ac458d13 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -116,6 +116,11 @@ export async function getItemQtyMap(doc: SalesInvoice): Promise { export async function getItemVisibility(fyo: Fyo): Promise { const posProfileName = fyo.singles.POSSettings?.posProfile as string; + const enableERPNextSync = fyo.singles.AccountingSettings?.enableERPNextSync; + + if (enableERPNextSync) { + return fyo.singles.POSSettings?.itemVisibilityERP as ItemVisibility; + } if (posProfileName) { const posProfile = await fyo.doc.getDoc( diff --git a/models/inventory/Point of Sale/POSSettings.ts b/models/inventory/Point of Sale/POSSettings.ts index 5fbcc9e6..3060b290 100644 --- a/models/inventory/Point of Sale/POSSettings.ts +++ b/models/inventory/Point of Sale/POSSettings.ts @@ -16,6 +16,7 @@ export class POSSettings extends Doc { itemWeightDigits?: number; defaultAccount?: string; itemVisibility?: string; + itemVisibilityERP?: 'ERP Sync Items'; posUI?: 'Classic' | 'Modern'; canChangeRate?: boolean; canEditDiscount?: boolean; @@ -46,6 +47,9 @@ export class POSSettings extends Doc { !this.fyo.singles.InventorySettings?.enableBarcodes || !this.weightEnabledBarcode, itemVisibility: () => - !this.fyo.singles.AccountingSettings?.enablePointOfSaleWithOutInventory, + !this.fyo.singles.AccountingSettings?.enablePointOfSaleWithOutInventory || + !!this.fyo.singles.AccountingSettings?.enableERPNextSync, + itemVisibilityERP: () => + !this.fyo.singles.AccountingSettings?.enableERPNextSync, }; } diff --git a/schemas/app/Item.json b/schemas/app/Item.json index 02a483fa..09f1bcca 100644 --- a/schemas/app/Item.json +++ b/schemas/app/Item.json @@ -172,6 +172,14 @@ "fieldtype": "Table", "target": "UOMConversionItem", "section": "Inventory" + }, + { + "fieldname": "datafromErp", + "fieldtype": "Check", + "hidden": true, + "default": false, + "section": "Default", + "readOnly": true } ], "quickEditFields": [ diff --git a/schemas/app/inventory/Point of Sale/POSSettings.json b/schemas/app/inventory/Point of Sale/POSSettings.json index 0d2887bb..f798a7af 100644 --- a/schemas/app/inventory/Point of Sale/POSSettings.json +++ b/schemas/app/inventory/Point of Sale/POSSettings.json @@ -96,13 +96,6 @@ "default": 0, "section": "Barcode" }, - { - "fieldname": "itemWeightDigits", - "label": "item Weight Digits", - "fieldtype": "Int", - "default": 0, - "section": "Barcode" - }, { "fieldname": "itemVisibility", "label": "Item Visibility", @@ -118,7 +111,27 @@ } ], "default": "Inventory Items", - "required": true, + "section": "Default" + }, + { + "fieldname": "itemVisibilityERP", + "label": "Item Visibility", + "fieldtype": "Select", + "options": [ + { + "value": "ERP Sync Items", + "label": "ERP Sync Items" + }, + { + "value": "Inventory Items", + "label": "Inventory Items" + }, + { + "value": "Non-Inventory Items", + "label": "Non-Inventory Items" + } + ], + "default": "ERP Sync Items", "section": "Default" }, { diff --git a/src/components/POS/Classic/ItemsGrid.vue b/src/components/POS/Classic/ItemsGrid.vue index 08c289a2..8847e941 100644 --- a/src/components/POS/Classic/ItemsGrid.vue +++ b/src/components/POS/Classic/ItemsGrid.vue @@ -63,6 +63,7 @@

@@ -81,13 +81,20 @@ export default defineComponent({ props: { items: Array, itemQtyMap: Object, + itemVisibility: { + type: String, + default: 'Inventory Items', + }, }, computed: { ratio() { + if (this.itemVisibility === 'ERP Sync Items') { + return [1, 1.5, 0.8]; + } return [1, 1, 1, 0.7]; }, tableFields() { - return [ + const fields = [ { fieldname: 'name', fieldtype: 'Data', @@ -102,13 +109,6 @@ export default defineComponent({ fieldtype: 'Currency', readOnly: true, }, - { - fieldname: 'availableQty', - label: 'Qty', - placeholder: 'Available Qty', - fieldtype: 'Float', - readOnly: true, - }, { fieldname: 'unit', label: 'Unit', @@ -118,6 +118,18 @@ export default defineComponent({ readOnly: true, }, ] as Field[]; + + if (this.itemVisibility !== 'ERP Sync Items') { + fields.splice(2, 0, { + fieldname: 'availableQty', + label: 'Qty', + placeholder: 'Available Qty', + fieldtype: 'Float', + readOnly: true, + }); + } + + return fields; }, }, methods: { diff --git a/src/components/POS/Modern/ModernPOSItemsGrid.vue b/src/components/POS/Modern/ModernPOSItemsGrid.vue index 0c06aed3..9df82aad 100644 --- a/src/components/POS/Modern/ModernPOSItemsGrid.vue +++ b/src/components/POS/Modern/ModernPOSItemsGrid.vue @@ -66,6 +66,7 @@

@@ -113,7 +113,7 @@ :key="df.fieldname" size="large" :df="df" - :value="row[df.fieldname]" + :value="(row as POSItem)[df.fieldname as keyof POSItem]" :readOnly="true" /> @@ -137,13 +137,20 @@ export default defineComponent({ props: { items: Array, itemQtyMap: Object, + itemVisibility: { + type: String, + default: 'Inventory Items', + }, }, computed: { ratio() { - return [1, 1, 0.6, 0.7]; + if (this.itemVisibility === 'ERP Sync Items') { + return [1, 1.5, 0.8]; + } + return [1, 1, 1, 0.7]; }, tableFields() { - return [ + const fields = [ { fieldname: 'name', fieldtype: 'Data', @@ -158,13 +165,6 @@ export default defineComponent({ fieldtype: 'Currency', readOnly: true, }, - { - fieldname: 'availableQty', - label: t`Qty`, - placeholder: 'Available Qty', - fieldtype: 'Float', - readOnly: true, - }, { fieldname: 'unit', label: t`Unit`, @@ -174,6 +174,18 @@ export default defineComponent({ readOnly: true, }, ] as Field[]; + + if (this.itemVisibility !== 'ERP Sync Items') { + fields.splice(2, 0, { + fieldname: 'availableQty', + label: t`Qty`, + placeholder: 'Available Qty', + fieldtype: 'Float', + readOnly: true, + }); + } + + return fields; }, firstColumnItems() { return this.items?.slice(0, Math.ceil(this.items.length / 2)); diff --git a/src/components/POS/types.ts b/src/components/POS/types.ts index 21023acb..cc49e511 100644 --- a/src/components/POS/types.ts +++ b/src/components/POS/types.ts @@ -10,7 +10,7 @@ export type ItemGroupMap = Record; export type DiscountType = 'percent' | 'amount'; -export type ItemVisibility = 'Inventory Items' | 'Non-Inventory Items' +export type ItemVisibility = 'Inventory Items' | 'Non-Inventory Items' | 'ERP Sync Items' export const modalNames = [ 'Keyboard', diff --git a/src/pages/POS/ClassicPOS.vue b/src/pages/POS/ClassicPOS.vue index de59942f..23832681 100644 --- a/src/pages/POS/ClassicPOS.vue +++ b/src/pages/POS/ClassicPOS.vue @@ -134,6 +134,7 @@ v-if="tableView" :items="items" :item-qty-map="itemQuantityMap as ItemQtyMap" + :item-visibility="itemVisibility" @add-item="(item) => emitEvent('addItem', item)" /> @@ -141,6 +142,7 @@ v-else :items="items" :item-qty-map="itemQuantityMap as ItemQtyMap" + :item-visibility="itemVisibility" @add-item="(item) => emitEvent('addItem', item)" /> @@ -483,6 +485,10 @@ export default defineComponent({ type: Array as PropType, default: () => [], }, + itemVisibility: { + type: String, + default: 'Inventory Items', + }, profile: { type: Object as PropType, required: false, diff --git a/src/pages/POS/ModernPOS.vue b/src/pages/POS/ModernPOS.vue index 7d278ee8..71723617 100644 --- a/src/pages/POS/ModernPOS.vue +++ b/src/pages/POS/ModernPOS.vue @@ -345,6 +345,7 @@ v-if="tableView" :items="items" :item-qty-map="itemQuantityMap as ItemQtyMap" + :item-visibility="itemVisibility" @add-item="(item:string) => emitEvent('addItem', item)" /> @@ -352,6 +353,7 @@ v-else :items="items" :item-qty-map="itemQuantityMap as ItemQtyMap" + :item-visibility="itemVisibility" @add-item="(item:string) => emitEvent('addItem', item)" /> @@ -489,6 +491,10 @@ export default defineComponent({ type: Array as PropType, default: () => [], }, + itemVisibility: { + type: String, + default: 'Inventory Items', + }, profile: { type: Object as PropType, required: false, diff --git a/src/pages/POS/POS.vue b/src/pages/POS/POS.vue index cdd6b032..154a85dd 100644 --- a/src/pages/POS/POS.vue +++ b/src/pages/POS/POS.vue @@ -27,6 +27,7 @@ :selected-item-group="selectedItemGroup" :is-pos-shift-open="isPosShiftOpen" :items="(items as [] as POSItem[])" + :item-visibility="itemVisibility" :sinv-doc="(sinvDoc as SalesInvoice)" :disable-pay-button="disablePayButton" :open-payment-modal="openPaymentModal" @@ -83,6 +84,7 @@ :selected-item-group="selectedItemGroup" :is-pos-shift-open="isPosShiftOpen" :items="(items as [] as POSItem[])" + :item-visibility="itemVisibility" :sinv-doc="(sinvDoc as SalesInvoice)" :disable-pay-button="disablePayButton" :open-payment-modal="openPaymentModal" @@ -167,6 +169,7 @@ import { getItemRateFromPriceList, getItemVisibility, } from 'models/helpers'; +import { ItemVisibility } from 'src/components/POS/types'; import { POSItem, ItemQtyMap, @@ -262,6 +265,7 @@ export default defineComponent({ quickQtyKeyUpHandler: null as ((e: KeyboardEvent) => void) | null, selectedItemForBatch: '' as string, pendingBatchItem: null as { item: POSItem; quantity: number } | null, + itemVisibilityValue: 'Inventory Items' as ItemVisibility, }; }, computed: { @@ -271,6 +275,9 @@ export default defineComponent({ return !!fyo.singles.AccountingSettings?.enableDiscounting; }, isPosShiftOpen: () => !!fyo.singles.POSSettings?.isShiftOpen, + itemVisibility() { + return this.itemVisibilityValue; + }, disablePayButton(): boolean { if (!this.sinvDoc.items?.length || !this.sinvDoc.party) { return true; @@ -295,6 +302,7 @@ export default defineComponent({ async mounted() { await this.setItems(); await this.loadPOSProfile(); + this.itemVisibilityValue = await getItemVisibility(this.fyo); }, async activated() { toggleSidebar(false); @@ -694,8 +702,11 @@ export default defineComponent({ if (itemVisibility === 'Inventory Items') { filters.trackItem = true; - } else { + } else if (itemVisibility === 'ERP Sync Items') { + filters.datafromErp = true; + } else if (itemVisibility === 'Non-Inventory Items') { filters.trackItem = false; + filters.datafromErp = false; } if (this.selectedItemGroup) { diff --git a/src/utils/erpnextSync.ts b/src/utils/erpnextSync.ts index 6197767f..728c2836 100644 --- a/src/utils/erpnextSync.ts +++ b/src/utils/erpnextSync.ts @@ -150,6 +150,10 @@ export async function syncDocumentsFromERPNext(fyo: Fyo) { continue; } + if (getDocTypeName(doc) === ModelNameEnum.Item) { + doc.datafromErp = true; + } + try { if ((doc.fbooksDocName as string) || (doc.name as string)) { const isDocExists = await fyo.db.exists( @@ -239,6 +243,10 @@ async function createNewDocument( token: string, deviceID: string ) { + if (getDocTypeName(doc) === ModelNameEnum.Item) { + doc.datafromErp = true; + } + const newDoc = fyo.doc.getNewDoc(getDocTypeName(doc), doc); await performPreSync(fyo, doc); await appendDocValues(newDoc as DocValueMap, doc); @@ -447,8 +455,10 @@ async function updateExistingDocument( token: string, deviceID: string ) { + const docType = getDocTypeName(doc); + const existingDoc = await fyo.doc.getDoc( - getDocTypeName(doc), + docType, (doc.fbooksDocName as string) || (doc.name as string) ); @@ -533,6 +543,10 @@ export async function performInitialFullSync(fyo: Fyo) { if (docsByType[docType] && docsByType[docType].length > 0) { for (const doc of docsByType[docType]) { try { + if (docType === ModelNameEnum.Item) { + doc.datafromErp = true; + } + const isDocExists = await fyo.db.exists( docType, (doc.fbooksDocName as string) || (doc.name as string)