From 08cbb4569cfc20c984f3a2410a5634f9ca856c11 Mon Sep 17 00:00:00 2001 From: Gadha2311 Date: Thu, 29 Jan 2026 13:06:22 +0530 Subject: [PATCH] fix: added erp item visibility --- fyo/model/doc.ts | 35 +++++++++++++++++-- .../ERPNextSyncSettings.ts | 3 ++ models/baseModels/InvoiceItem/InvoiceItem.ts | 26 ++++++++++++-- models/baseModels/Item/Item.ts | 2 ++ models/helpers.ts | 6 ++++ models/inventory/Point of Sale/POSSettings.ts | 5 ++- schemas/app/Item.json | 7 ++++ .../inventory/Point of Sale/POSSettings.json | 22 ++++++++++++ src/components/POS/Classic/ItemsGrid.vue | 5 +++ src/components/POS/Classic/ItemsTable.vue | 28 ++++++++++----- .../POS/Modern/ModernPOSItemsGrid.vue | 5 +++ .../POS/Modern/ModernPOSItemsTable.vue | 30 +++++++++++----- src/components/POS/types.ts | 2 +- src/pages/POS/ClassicPOS.vue | 6 ++++ src/pages/POS/ModernPOS.vue | 6 ++++ src/pages/POS/POS.vue | 10 ++++++ src/utils/erpnextSync.ts | 4 +++ 17 files changed, 178 insertions(+), 24 deletions(-) diff --git a/fyo/model/doc.ts b/fyo/model/doc.ts index 14965ceb..50f7bb7b 100644 --- a/fyo/model/doc.ts +++ b/fyo/model/doc.ts @@ -920,6 +920,35 @@ export class Doc extends Observable { return this; } + async _hasERPSyncableItems(): Promise { + const isSalesInvoice = this.schemaName === ModelNameEnum.SalesInvoice; + if (!isSalesInvoice) { + return true; + } + + const items = (this.get('items') as Doc[]) ?? []; + + for (const item of items) { + const itemName = item.get('item') as string; + if (!itemName) { + continue; + } + + try { + const itemDoc = await this.fyo.doc.getDoc('Item', itemName); + const isInventoryItem = !!itemDoc.get('trackItem'); + const isFromERP = !!itemDoc.get('datafromErp'); + + if (!isInventoryItem && isFromERP) { + return true; + } + } catch (err) { + continue; + } + } + + return false; + } async sync(): Promise { this._syncing = true; @@ -936,10 +965,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/ERPNextSyncSettings/ERPNextSyncSettings.ts b/models/baseModels/ERPNextSyncSettings/ERPNextSyncSettings.ts index b163e153..ee17eebc 100644 --- a/models/baseModels/ERPNextSyncSettings/ERPNextSyncSettings.ts +++ b/models/baseModels/ERPNextSyncSettings/ERPNextSyncSettings.ts @@ -40,6 +40,9 @@ export class ERPNextSyncSettings extends Doc { batchSyncType: () => { return !this.fyo.singles.InventorySettings?.enableBatches; }, + // syncDataFromServer: () => { + // return !this.deviceID; + // }, }; async change(ch: ChangeArg) { diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts index 590702d2..14ed7ef0 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,28 @@ 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 === 'ERP Sync Items') { + filters.datafromErp = true; + } + } + + 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..5f349386 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 = { @@ -237,5 +238,6 @@ export class Item extends Doc { trackItem: () => this.inserted, hasBatch: () => this.inserted, hasSerialNumber: () => this.inserted, + datafromErp: () => true, }; } diff --git a/models/helpers.ts b/models/helpers.ts index 07e13435..37e8744c 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -116,6 +116,12 @@ 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) { + // When ERP sync is enabled, use itemVisibilityERP from POSSettings + 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..2a70b60a 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,8 @@ export class POSSettings extends Doc { !this.fyo.singles.InventorySettings?.enableBarcodes || !this.weightEnabledBarcode, itemVisibility: () => - !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..d97b7677 100644 --- a/schemas/app/Item.json +++ b/schemas/app/Item.json @@ -172,6 +172,13 @@ "fieldtype": "Table", "target": "UOMConversionItem", "section": "Inventory" + }, + { + "fieldname": "datafromErp", + "fieldtype": "Check", + "hidden": false, + "default": false, + "section": "Default" } ], "quickEditFields": [ diff --git a/schemas/app/inventory/Point of Sale/POSSettings.json b/schemas/app/inventory/Point of Sale/POSSettings.json index 0d2887bb..657a28eb 100644 --- a/schemas/app/inventory/Point of Sale/POSSettings.json +++ b/schemas/app/inventory/Point of Sale/POSSettings.json @@ -121,6 +121,28 @@ "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", + "required": true, + "section": "Default" + }, { "fieldname": "canChangeRate", "label": "Can Change Rate", diff --git a/src/components/POS/Classic/ItemsGrid.vue b/src/components/POS/Classic/ItemsGrid.vue index 08c289a2..0081f134 100644 --- a/src/components/POS/Classic/ItemsGrid.vue +++ b/src/components/POS/Classic/ItemsGrid.vue @@ -63,6 +63,7 @@

@@ -141,6 +142,7 @@ v-else :items="items" :item-qty-map="itemQuantityMap as ItemQtyMap" + :is-erp-sync="isErpSync" @add-item="(item) => emitEvent('addItem', item)" /> @@ -483,6 +485,10 @@ export default defineComponent({ type: Array as PropType, default: () => [], }, + isErpSync: { + type: Boolean, + default: false, + }, profile: { type: Object as PropType, required: false, diff --git a/src/pages/POS/ModernPOS.vue b/src/pages/POS/ModernPOS.vue index 7d278ee8..df46a2fe 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" + :is-erp-sync="isErpSync" @add-item="(item:string) => emitEvent('addItem', item)" /> @@ -352,6 +353,7 @@ v-else :items="items" :item-qty-map="itemQuantityMap as ItemQtyMap" + :is-erp-sync="isErpSync" @add-item="(item:string) => emitEvent('addItem', item)" /> @@ -489,6 +491,10 @@ export default defineComponent({ type: Array as PropType, default: () => [], }, + isErpSync: { + type: Boolean, + default: false, + }, profile: { type: Object as PropType, required: false, diff --git a/src/pages/POS/POS.vue b/src/pages/POS/POS.vue index cdd6b032..cc9c1799 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[])" + :is-erp-sync="isErpSync" :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[])" + :is-erp-sync="isErpSync" :sinv-doc="(sinvDoc as SalesInvoice)" :disable-pay-button="disablePayButton" :open-payment-modal="openPaymentModal" @@ -262,6 +264,7 @@ export default defineComponent({ quickQtyKeyUpHandler: null as ((e: KeyboardEvent) => void) | null, selectedItemForBatch: '' as string, pendingBatchItem: null as { item: POSItem; quantity: number } | null, + isErpSyncValue: false, }; }, computed: { @@ -271,6 +274,9 @@ export default defineComponent({ return !!fyo.singles.AccountingSettings?.enableDiscounting; }, isPosShiftOpen: () => !!fyo.singles.POSSettings?.isShiftOpen, + isErpSync() { + return this.isErpSyncValue; + }, disablePayButton(): boolean { if (!this.sinvDoc.items?.length || !this.sinvDoc.party) { return true; @@ -295,6 +301,7 @@ export default defineComponent({ async mounted() { await this.setItems(); await this.loadPOSProfile(); + this.isErpSyncValue = !!fyo.singles.AccountingSettings?.enableERPNextSync; }, async activated() { toggleSidebar(false); @@ -694,8 +701,11 @@ export default defineComponent({ if (itemVisibility === 'Inventory Items') { filters.trackItem = true; + } else if (itemVisibility === 'ERP Sync Items') { + filters.datafromErp = true; } else { filters.trackItem = false; + filters.datafromErp = false; } if (this.selectedItemGroup) { diff --git a/src/utils/erpnextSync.ts b/src/utils/erpnextSync.ts index 6197767f..5cfb7cc5 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(