mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
Merge pull request #1435 from Gadha2311/erp-itemvisibility
fix: added erp item visibility
This commit is contained in:
+24
-2
@@ -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);
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -30,6 +30,7 @@ export class Item extends Doc {
|
||||
hsnCode?: number;
|
||||
hasSerialNumber?: boolean;
|
||||
serialNumberSeries?: string;
|
||||
datafromErp?: boolean;
|
||||
uomConversions: UOMConversionItem[] = [];
|
||||
|
||||
formulas: FormulaMap = {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user