Merge pull request #1435 from Gadha2311/erp-itemvisibility

fix: added erp item visibility
This commit is contained in:
Able k Saju
2026-02-12 09:54:56 +05:30
committed by GitHub
16 changed files with 186 additions and 37 deletions
+24 -2
View File
@@ -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<DocValue | Doc[]> {
/* eslint-disable @typescript-eslint/no-floating-promises */
@@ -920,6 +921,25 @@ export class Doc extends Observable<DocValue | Doc[]> {
return this;
}
async _hasERPSyncableItems(): Promise<boolean> {
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<Doc> {
this._syncing = true;
@@ -936,10 +956,12 @@ export class Doc extends Observable<DocValue | Doc[]> {
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);
+28 -3
View File
@@ -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<QueryFilter> => {
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, {
+1
View File
@@ -30,6 +30,7 @@ export class Item extends Doc {
hsnCode?: number;
hasSerialNumber?: boolean;
serialNumberSeries?: string;
datafromErp?: boolean;
uomConversions: UOMConversionItem[] = [];
formulas: FormulaMap = {
+5
View File
@@ -116,6 +116,11 @@ export async function getItemQtyMap(doc: SalesInvoice): Promise<ItemQtyMap> {
export async function getItemVisibility(fyo: Fyo): Promise<ItemVisibility> {
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(
@@ -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,
};
}
+8
View File
@@ -172,6 +172,14 @@
"fieldtype": "Table",
"target": "UOMConversionItem",
"section": "Inventory"
},
{
"fieldname": "datafromErp",
"fieldtype": "Check",
"hidden": true,
"default": false,
"section": "Default",
"readOnly": true
}
],
"quickEditFields": [
@@ -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"
},
{
+5
View File
@@ -63,6 +63,7 @@
</p>
</div>
<p
v-if="itemVisibility !== 'ERP Sync Items'"
class="
absolute
top-1
@@ -111,6 +112,10 @@ export default defineComponent({
itemQtyMap: {
type: Object,
},
itemVisibility: {
type: String,
default: 'Inventory Items',
},
},
methods: {
getExtractedWords(item: string) {
+21 -9
View File
@@ -59,7 +59,7 @@
size="large"
class=""
:df="df"
:value="row[df.fieldname]"
:value="(row as POSItem)[df.fieldname as keyof POSItem]"
:readOnly="true"
/>
</Row>
@@ -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: {
@@ -66,6 +66,7 @@
</p>
</div>
<p
v-if="itemVisibility !== 'ERP Sync Items'"
class="
w-6
h-6
@@ -114,6 +115,10 @@ export default defineComponent({
itemQtyMap: {
type: Object,
},
itemVisibility: {
type: String,
default: 'Inventory Items',
},
},
methods: {
getExtractedWords(item: string) {
@@ -54,7 +54,7 @@
:key="df.fieldname"
size="large"
:df="df"
:value="row[df.fieldname]"
:value="(row as POSItem)[df.fieldname as keyof POSItem]"
:readOnly="true"
/>
</Row>
@@ -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"
/>
</Row>
@@ -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));
+1 -1
View File
@@ -10,7 +10,7 @@ export type ItemGroupMap = Record<string, string>;
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',
+6
View File
@@ -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<POSItem[] | undefined>,
default: () => [],
},
itemVisibility: {
type: String,
default: 'Inventory Items',
},
profile: {
type: Object as PropType<POSProfile>,
required: false,
+6
View File
@@ -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<POSItem[] | undefined>,
default: () => [],
},
itemVisibility: {
type: String,
default: 'Inventory Items',
},
profile: {
type: Object as PropType<POSProfile>,
required: false,
+12 -1
View File
@@ -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) {
+15 -1
View File
@@ -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)