mirror of
https://github.com/frappe/books.git
synced 2025-01-09 01:44:15 +00:00
feat: erpnext sync
This commit is contained in:
parent
d304e9cd38
commit
603f2d972f
fyo/model
main
models
src
utils
@ -44,6 +44,8 @@ import {
|
||||
ValidationMap,
|
||||
} from './types';
|
||||
import { validateOptions, validateRequired } from './validationFunction';
|
||||
import { getShouldDocSyncToERPNext } from 'src/utils/erpnextSync';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
|
||||
export class Doc extends Observable<DocValue | Doc[]> {
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
@ -247,6 +249,22 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
return true;
|
||||
}
|
||||
|
||||
get shouldDocSyncToERPNext() {
|
||||
const syncEnabled = !!this.fyo.singles.ERPNextSyncSettings?.isEnabled;
|
||||
if (!syncEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.schemaName || !this.fyo.singles.ERPNextSyncSettings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getShouldDocSyncToERPNext(
|
||||
this.fyo.singles.ERPNextSyncSettings,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
_setValuesWithoutChecks(data: DocValueMap, convertToDocValue: boolean) {
|
||||
for (const field of this.schema.fields) {
|
||||
const { fieldname, fieldtype } = field;
|
||||
@ -912,6 +930,28 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
this._notInserted = false;
|
||||
await this.trigger('afterSync');
|
||||
this.fyo.doc.observer.trigger(`sync:${this.schemaName}`, this.name);
|
||||
|
||||
if (this._addDocToSyncQueue && !!this.shouldDocSyncToERPNext) {
|
||||
const isDocExistsInQueue = await this.fyo.db.getAll(
|
||||
ModelNameEnum.ERPNextSyncQueue,
|
||||
{
|
||||
filters: {
|
||||
referenceType: this.schemaName,
|
||||
documentName: this.name as string,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!isDocExistsInQueue.length) {
|
||||
this.fyo.doc
|
||||
.getNewDoc(ModelNameEnum.ERPNextSyncQueue, {
|
||||
referenceType: this.schemaName,
|
||||
documentName: this.name,
|
||||
})
|
||||
.sync();
|
||||
}
|
||||
}
|
||||
|
||||
this._syncing = false;
|
||||
return doc;
|
||||
}
|
||||
|
10
main/api.ts
Normal file
10
main/api.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import fetch, { RequestInit } from 'node-fetch';
|
||||
|
||||
export async function sendAPIRequest(
|
||||
endpoint: string,
|
||||
options: RequestInit | undefined
|
||||
) {
|
||||
return (await fetch(endpoint, options)).json() as unknown as {
|
||||
[key: string]: string | number | boolean;
|
||||
}[];
|
||||
}
|
@ -180,6 +180,18 @@ const ipc = {
|
||||
await ipcRenderer.invoke(IPC_ACTIONS.SEND_ERROR, body);
|
||||
},
|
||||
|
||||
async sendAPIRequest(endpoint: string, options: RequestInit | undefined) {
|
||||
return (await ipcRenderer.invoke(
|
||||
IPC_ACTIONS.SEND_API_REQUEST,
|
||||
endpoint,
|
||||
options
|
||||
)) as Promise<
|
||||
{
|
||||
[key: string]: string | number | boolean | Date | object | object[];
|
||||
}[]
|
||||
>;
|
||||
},
|
||||
|
||||
registerMainProcessErrorListener(listener: IPCRendererListener) {
|
||||
ipcRenderer.on(IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR, listener);
|
||||
},
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
setAndGetCleanedConfigFiles,
|
||||
} from './helpers';
|
||||
import { saveHtmlAsPdf } from './saveHtmlAsPdf';
|
||||
import { sendAPIRequest } from './api';
|
||||
|
||||
export default function registerIpcMainActionListeners(main: Main) {
|
||||
ipcMain.handle(IPC_ACTIONS.CHECK_DB_ACCESS, async (_, filePath: string) => {
|
||||
@ -209,6 +210,13 @@ export default function registerIpcMainActionListeners(main: Main) {
|
||||
return getTemplates();
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
IPC_ACTIONS.SEND_API_REQUEST,
|
||||
async (e, endpoint: string, options: RequestInit | undefined) => {
|
||||
return sendAPIRequest(endpoint, options);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Database Related Actions
|
||||
*/
|
||||
|
@ -1257,6 +1257,30 @@ export function removeFreeItems(sinvDoc: SalesInvoice) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePricingRule(sinvDoc: SalesInvoice) {
|
||||
const applicablePricingRuleNames = await getPricingRule(sinvDoc);
|
||||
|
||||
if (!applicablePricingRuleNames || !applicablePricingRuleNames.length) {
|
||||
sinvDoc.pricingRuleDetail = undefined;
|
||||
sinvDoc.isPricingRuleApplied = false;
|
||||
removeFreeItems(sinvDoc);
|
||||
return;
|
||||
}
|
||||
|
||||
const appliedPricingRuleCount = sinvDoc?.items?.filter(
|
||||
(val) => val.isFreeItem
|
||||
).length;
|
||||
|
||||
setTimeout(() => {
|
||||
void (async () => {
|
||||
if (appliedPricingRuleCount !== applicablePricingRuleNames?.length) {
|
||||
await sinvDoc.appendPricingRuleDetail(applicablePricingRuleNames);
|
||||
await sinvDoc.applyProductDiscount();
|
||||
}
|
||||
})();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
export function getPricingRulesConflicts(
|
||||
pricingRules: PricingRule[]
|
||||
): undefined | boolean {
|
||||
|
@ -61,7 +61,9 @@ export enum ModelNameEnum {
|
||||
POSSettings = 'POSSettings',
|
||||
POSShift = 'POSShift',
|
||||
|
||||
ERPNextSyncSettings= 'ERPNextSyncSettings'
|
||||
ERPNextSyncSettings= 'ERPNextSyncSettings',
|
||||
ERPNextSyncQueue = 'ERPNextSyncQueue',
|
||||
FetchFromERPNextQueue = 'FetchFromERPNextQueue',
|
||||
}
|
||||
|
||||
export type ModelName = keyof typeof ModelNameEnum;
|
||||
|
@ -70,6 +70,7 @@ import { Shortcuts } from './utils/shortcuts';
|
||||
import { routeTo } from './utils/ui';
|
||||
import { useKeys } from './utils/vueUtils';
|
||||
import { setDarkMode } from 'src/utils/theme';
|
||||
import { initERPNSync, updateERPNSyncSettings } from './utils/erpnextSync';
|
||||
|
||||
enum Screen {
|
||||
Desk = 'Desk',
|
||||
@ -224,6 +225,8 @@ export default defineComponent({
|
||||
|
||||
await initializeInstance(filePath, false, countryCode, fyo);
|
||||
await updatePrintTemplates(fyo);
|
||||
await updateERPNSyncSettings(fyo);
|
||||
initERPNSync(fyo);
|
||||
await this.setDesk(filePath);
|
||||
},
|
||||
async handleConnectionFailed(error: Error, actionSymbol: symbol) {
|
||||
|
6
src/utils/api.ts
Normal file
6
src/utils/api.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export async function sendAPIRequest(
|
||||
endpoint: string,
|
||||
options: RequestInit | undefined
|
||||
) {
|
||||
return await ipc.sendAPIRequest(endpoint, options);
|
||||
}
|
631
src/utils/erpnextSync.ts
Normal file
631
src/utils/erpnextSync.ts
Normal file
@ -0,0 +1,631 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { sendAPIRequest } from './api';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { ERPNextSyncSettings } from 'models/baseModels/ERPNextSyncSettings/ERPNextSyncSettings';
|
||||
import { DocValueMap } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { ERPNextSyncQueue } from 'models/baseModels/ERPNextSyncQueue/ERPNextSyncQueue';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
|
||||
export async function updateERPNSyncSettings(fyo: Fyo) {
|
||||
const syncSettingsDoc = (await fyo.doc.getDoc(
|
||||
ModelNameEnum.ERPNextSyncSettings
|
||||
)) as ERPNextSyncSettings;
|
||||
|
||||
const endpoint = syncSettingsDoc.endpoint;
|
||||
const authToken = syncSettingsDoc.authToken;
|
||||
|
||||
if (!endpoint || !authToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await getERPNSyncSettings(endpoint, authToken);
|
||||
if (!res || !res.message || !res.message.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
await syncSettingsDoc.setMultiple(parseSyncSettingsData(res));
|
||||
await syncSettingsDoc.sync();
|
||||
}
|
||||
|
||||
async function getERPNSyncSettings(
|
||||
endpoint: string,
|
||||
token: string
|
||||
): Promise<ERPNextSyncSettingsAPIResponse | undefined> {
|
||||
try {
|
||||
return (await sendAPIRequest(
|
||||
`${endpoint}/api/method/books_integration.api.sync_settings`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
},
|
||||
}
|
||||
)) as unknown as ERPNextSyncSettingsAPIResponse;
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function initERPNSync(fyo: Fyo) {
|
||||
const isSyncEnabled = fyo.singles.ERPNextSyncSettings?.isEnabled;
|
||||
if (!isSyncEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncInterval = fyo.singles.ERPNextSyncSettings?.dataSyncInterval;
|
||||
|
||||
if (!syncInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
setInterval(async () => {
|
||||
await syncFetchFromERPNextQueue(fyo);
|
||||
await syncDocumentsFromERPNext(fyo);
|
||||
await syncDocumentsToERPNext(fyo);
|
||||
}, syncInterval);
|
||||
}
|
||||
|
||||
export async function syncDocumentsFromERPNext(fyo: Fyo) {
|
||||
const isEnabled = fyo.singles.ERPNextSyncSettings?.isEnabled;
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = fyo.singles.ERPNextSyncSettings?.authToken;
|
||||
const endpoint = fyo.singles.ERPNextSyncSettings?.endpoint;
|
||||
|
||||
if (!token || !endpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docsToSync = await getDocsFromERPNext(endpoint, token);
|
||||
|
||||
if (!docsToSync || !docsToSync.message.success || !docsToSync.message.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const doc of docsToSync.message.data) {
|
||||
if (!(getDocTypeName(doc) in ModelNameEnum)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if ((doc.fbooksDocName as string) || (doc.name as string)) {
|
||||
const isDocExists = await fyo.db.exists(
|
||||
getDocTypeName(doc),
|
||||
(doc.fbooksDocName as string) || (doc.name as string)
|
||||
);
|
||||
|
||||
if (isDocExists) {
|
||||
const existingDoc = await fyo.doc.getDoc(
|
||||
getDocTypeName(doc),
|
||||
(doc.fbooksDocName as string) || (doc.name as string)
|
||||
);
|
||||
|
||||
await existingDoc.setMultiple(doc);
|
||||
await performPreSync(fyo, doc, existingDoc);
|
||||
existingDoc._addDocToSyncQueue = false;
|
||||
|
||||
await existingDoc.sync();
|
||||
|
||||
if (doc.submitted) {
|
||||
await existingDoc.submit();
|
||||
}
|
||||
|
||||
if (doc.cancelled) {
|
||||
await existingDoc.cancel();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
const newDoc = fyo.doc.getNewDoc(getDocTypeName(doc), doc);
|
||||
|
||||
await performPreSync(fyo, doc, newDoc);
|
||||
newDoc._addDocToSyncQueue = false;
|
||||
|
||||
await newDoc.sync();
|
||||
|
||||
if (doc.submitted) {
|
||||
await newDoc.submit();
|
||||
}
|
||||
|
||||
if (doc.cancelled) {
|
||||
await newDoc.cancel();
|
||||
}
|
||||
|
||||
await afterDocSync(
|
||||
endpoint,
|
||||
token,
|
||||
doc,
|
||||
doc.name as string,
|
||||
newDoc.name as string
|
||||
);
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function performPreSync(fyo: Fyo, doc: DocValueMap) {
|
||||
switch (doc.doctype) {
|
||||
case ModelNameEnum.Item:
|
||||
const isUnitExists = await fyo.db.exists(
|
||||
ModelNameEnum.UOM,
|
||||
doc.unit as string
|
||||
);
|
||||
|
||||
const isUnitExistsInQueue = (
|
||||
await fyo.db.getAll(ModelNameEnum.FetchFromERPNextQueue, {
|
||||
filters: {
|
||||
referenceType: ModelNameEnum.UOM,
|
||||
documentName: doc.unit as string,
|
||||
},
|
||||
})
|
||||
).length;
|
||||
|
||||
if (!isUnitExists && !isUnitExistsInQueue) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.UOM,
|
||||
documentName: doc.unit,
|
||||
});
|
||||
}
|
||||
|
||||
if (doc.uomConversions) {
|
||||
for (const row of doc.uomConversions as DocValueMap[]) {
|
||||
const isUnitExists = await fyo.db.exists(
|
||||
ModelNameEnum.UOM,
|
||||
row.uom as string
|
||||
);
|
||||
|
||||
if (!isUnitExists && !isUnitExistsInQueue) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.UOM,
|
||||
documentName: row.uom,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case ModelNameEnum.Party:
|
||||
const isAddressExists = await fyo.db.exists(
|
||||
ModelNameEnum.Address,
|
||||
doc.addressName as string
|
||||
);
|
||||
|
||||
if (!isAddressExists) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.Address,
|
||||
documentName: doc.addressName,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
case ModelNameEnum.SalesInvoice:
|
||||
return await preSyncSalesInvoice(fyo, doc as SalesInvoice);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function preSyncSalesInvoice(fyo: Fyo, doc: SalesInvoice) {
|
||||
const isPartyExists = await fyo.db.exists(
|
||||
ModelNameEnum.Party,
|
||||
doc.party as string
|
||||
);
|
||||
|
||||
if (!isPartyExists) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.Party,
|
||||
documentName: doc.party,
|
||||
});
|
||||
}
|
||||
|
||||
if (doc.items) {
|
||||
for (const item of doc.items) {
|
||||
const isUnitExists = await fyo.db.exists(ModelNameEnum.UOM, item.unit);
|
||||
if (!isUnitExists) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.UOM,
|
||||
documentName: item.unit,
|
||||
});
|
||||
}
|
||||
|
||||
const isItemExists = await fyo.db.exists(ModelNameEnum.Item, item.item);
|
||||
if (!isItemExists) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.Item,
|
||||
documentName: item.item,
|
||||
});
|
||||
}
|
||||
|
||||
if (item.batch) {
|
||||
const isBatchExists = await fyo.db.exists(
|
||||
ModelNameEnum.Batch,
|
||||
item.batch
|
||||
);
|
||||
|
||||
if (!isBatchExists) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.Batch,
|
||||
documentName: item.batch,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.priceList) {
|
||||
const isPriceListExists = await fyo.db.exists(
|
||||
ModelNameEnum.PriceList,
|
||||
doc.priceList
|
||||
);
|
||||
|
||||
if (!isPriceListExists) {
|
||||
await addToFetchFromERPNextQueue(fyo, {
|
||||
referenceType: ModelNameEnum.PriceList,
|
||||
documentName: doc.priceList,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function addToFetchFromERPNextQueue(fyo: Fyo, data: DocValueMap) {
|
||||
await fyo.doc.getNewDoc(ModelNameEnum.FetchFromERPNextQueue, data).sync();
|
||||
}
|
||||
|
||||
export async function syncDocumentsToERPNext(fyo: Fyo) {
|
||||
const isEnabled = fyo.singles.ERPNextSyncSettings?.isEnabled;
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = fyo.singles.ERPNextSyncSettings?.authToken as string;
|
||||
const endpoint = fyo.singles.ERPNextSyncSettings?.endpoint as string;
|
||||
|
||||
if (!token || !endpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docsToSync = [];
|
||||
const syncQueueItems = (await fyo.db.getAll(ModelNameEnum.ERPNextSyncQueue, {
|
||||
fields: ['referenceType', 'documentName'],
|
||||
order: 'desc',
|
||||
})) as ERPNextSyncQueue[];
|
||||
|
||||
if (!syncQueueItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const doc of syncQueueItems) {
|
||||
const referenceDoc = await fyo.doc.getDoc(
|
||||
doc.referenceType as ModelNameEnum,
|
||||
doc.documentName
|
||||
);
|
||||
|
||||
if (!referenceDoc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
docsToSync.push({
|
||||
doctype: getDocTypeName(referenceDoc),
|
||||
...referenceDoc.getValidDict(),
|
||||
});
|
||||
}
|
||||
|
||||
if (!docsToSync.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = (await sendAPIRequest(
|
||||
`${endpoint}/api/method/books_integration.api.insert_docs`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ payload: docsToSync }),
|
||||
}
|
||||
)) as unknown as InsertDocsAPIResponse;
|
||||
|
||||
if (res.message.success) {
|
||||
if (!res.message.success_log.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const doc of res.message.success_log) {
|
||||
const filteredLogDoc = await fyo.db.getAll(
|
||||
ModelNameEnum.ERPNextSyncQueue,
|
||||
{
|
||||
filters: {
|
||||
referenceType: getDocTypeName(doc),
|
||||
documentName: doc.name,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!filteredLogDoc.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const logDoc = await fyo.doc.getDoc(
|
||||
ModelNameEnum.ERPNextSyncQueue,
|
||||
filteredLogDoc[0].name as string
|
||||
);
|
||||
|
||||
await logDoc.delete();
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
async function syncFetchFromERPNextQueue(fyo: Fyo) {
|
||||
const docsInQueue = await fyo.db.getAll(ModelNameEnum.FetchFromERPNextQueue, {
|
||||
fields: ['referenceType', 'documentName'],
|
||||
});
|
||||
|
||||
if (!docsInQueue.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = fyo.singles.ERPNextSyncSettings?.authToken as string;
|
||||
const endpoint = fyo.singles.ERPNextSyncSettings?.endpoint as string;
|
||||
|
||||
if (!token || !endpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = (await sendAPIRequest(
|
||||
`${endpoint}/api/method/books_integration.api.sync_queue`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ records: docsInQueue }),
|
||||
}
|
||||
)) as unknown as ERPNSyncDocsResponse;
|
||||
|
||||
if (!res.message.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res.message.success_log) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const row of res.message.success_log) {
|
||||
const isDocExisitsInQueue = await fyo.db.getAll(
|
||||
ModelNameEnum.FetchFromERPNextQueue,
|
||||
{
|
||||
filters: {
|
||||
referenceType: row.doctype_name as string,
|
||||
documentName: row.document_name as string,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!isDocExisitsInQueue.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingDoc = await fyo.doc.getDoc(
|
||||
ModelNameEnum.FetchFromERPNextQueue,
|
||||
isDocExisitsInQueue[0].name as string
|
||||
);
|
||||
await existingDoc.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function getDocsFromERPNext(
|
||||
endpoint: string,
|
||||
token: string
|
||||
): Promise<ERPNSyncDocsResponse | undefined> {
|
||||
try {
|
||||
return (await sendAPIRequest(
|
||||
`${endpoint}/api/method/books_integration.api.sync_queue`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
},
|
||||
}
|
||||
)) as unknown as ERPNSyncDocsResponse;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function afterDocSync(
|
||||
endpoint: string,
|
||||
token: string,
|
||||
doc: Doc | DocValueMap,
|
||||
erpnDocName: string,
|
||||
fbooksDocName: string
|
||||
) {
|
||||
const res = await ipc.sendAPIRequest(
|
||||
`${endpoint}/api/method/books_integration.api.perform_aftersync`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
doctype: getDocTypeName(doc),
|
||||
nameInERPNext: erpnDocName,
|
||||
nameInFBooks: fbooksDocName,
|
||||
doc,
|
||||
}),
|
||||
}
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getShouldDocSyncToERPNext(
|
||||
syncSettings: ERPNextSyncSettings,
|
||||
doc: Doc
|
||||
): boolean {
|
||||
switch (doc.schemaName) {
|
||||
case ModelNameEnum.Payment:
|
||||
const isSalesPayment = doc.referenceType === ModelNameEnum.SalesInvoice;
|
||||
return (
|
||||
isSalesPayment && syncSettings.sinvPaymentType !== 'ERPNext to FBooks'
|
||||
);
|
||||
|
||||
case ModelNameEnum.Party:
|
||||
const isCustomer = doc.role !== 'Supplier';
|
||||
|
||||
if (isCustomer) {
|
||||
return (
|
||||
!!syncSettings.syncCustomer &&
|
||||
syncSettings.customerSyncType !== 'ERPNext to FBooks'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
!!syncSettings.syncSupplier &&
|
||||
syncSettings.supplierSyncType !== 'ERPNext to FBooks'
|
||||
);
|
||||
|
||||
case ModelNameEnum.PriceListItem:
|
||||
const isPriceListSyncEnabled = !!syncSettings.syncPriceList;
|
||||
|
||||
return (
|
||||
isPriceListSyncEnabled &&
|
||||
syncSettings.supplierSyncType !== 'ERPNext to FBooks'
|
||||
);
|
||||
|
||||
default:
|
||||
const schemaName =
|
||||
doc.schemaName[0].toLowerCase() + doc.schemaName.substring(1);
|
||||
|
||||
if (!syncSettings[`${schemaName}SyncType`]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return syncSettings[`${schemaName}SyncType`] !== 'ERPNext to FBooks';
|
||||
}
|
||||
}
|
||||
|
||||
function getDocTypeName(doc: DocValueMap | Doc): string {
|
||||
const doctype =
|
||||
doc.schemaName ?? doc.referenceType ?? (doc.doctype as string);
|
||||
|
||||
if (['Supplier', 'Customer'].includes(doctype as string)) {
|
||||
return ModelNameEnum.Party;
|
||||
}
|
||||
|
||||
if (doctype === 'Party') {
|
||||
if (doc.role && doc.role !== 'Both') {
|
||||
return doc.role as string;
|
||||
}
|
||||
}
|
||||
|
||||
return doctype as string;
|
||||
}
|
||||
|
||||
export interface InsertDocsAPIResponse {
|
||||
message: {
|
||||
success: boolean;
|
||||
success_log: { name: string; doctype: string }[];
|
||||
failed_log: { name: string; doctype: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ERPNSyncDocsResponse {
|
||||
message: {
|
||||
success: boolean;
|
||||
data: DocValueMap[];
|
||||
success_log?: DocValueMap[];
|
||||
failed_log?: DocValueMap[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ERPNextSyncSettingsAPIResponse {
|
||||
message: {
|
||||
success: boolean;
|
||||
app_version: string;
|
||||
data: {
|
||||
name: string;
|
||||
owner: string;
|
||||
modified: string;
|
||||
modified_by: string;
|
||||
docstatus: boolean;
|
||||
idx: string;
|
||||
enable_sync: boolean;
|
||||
sync_dependant_masters: boolean;
|
||||
sync_interval: number;
|
||||
sync_item: boolean;
|
||||
item_sync_type: string;
|
||||
sync_customer: boolean;
|
||||
customer_sync_type: string;
|
||||
sync_supplier: boolean;
|
||||
supplier_sync_type: string;
|
||||
sync_sales_invoice: boolean;
|
||||
sales_invoice_sync_type: string;
|
||||
sync_sales_payment: boolean;
|
||||
sales_payment_sync_type: string;
|
||||
sync_stock: boolean;
|
||||
stock_sync_type: string;
|
||||
sync_price_list: boolean;
|
||||
price_list_sync_type: string;
|
||||
sync_serial_number: boolean;
|
||||
serial_number_sync_type: string;
|
||||
sync_batches: boolean;
|
||||
batch_sync_type: string;
|
||||
sync_delivery_note: boolean;
|
||||
delivery_note_sync_type: string;
|
||||
doctype: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function parseSyncSettingsData(
|
||||
res: ERPNextSyncSettingsAPIResponse
|
||||
): DocValueMap {
|
||||
return {
|
||||
integrationAppVersion: res.message.app_version,
|
||||
isEnabled: !!res.message.data.enable_sync,
|
||||
dataSyncInterval: res.message.data.sync_interval,
|
||||
|
||||
syncItem: res.message.data.sync_item,
|
||||
itemSyncType: res.message.data.item_sync_type,
|
||||
|
||||
syncCustomer: res.message.data.sync_customer,
|
||||
customerSyncType: res.message.data.customer_sync_type,
|
||||
|
||||
syncSupplier: res.message.data.sync_supplier,
|
||||
supplierSyncType: res.message.data.supplier_sync_type,
|
||||
|
||||
syncSalesInvoice: res.message.data.sync_sales_invoice,
|
||||
salesInvoiceSyncType: res.message.data.sales_invoice_sync_type,
|
||||
|
||||
syncSalesInvoicePayment: res.message.data.sync_sales_payment,
|
||||
sinvPaymentSyncType: res.message.data.sales_payment_sync_type,
|
||||
|
||||
syncStockMovement: res.message.data.sync_stock,
|
||||
stockMovementSyncType: res.message.data.stock_sync_type,
|
||||
|
||||
syncPriceList: res.message.data.sync_price_list,
|
||||
priceListSyncType: res.message.data.price_list_sync_type,
|
||||
|
||||
syncSerialNumber: res.message.data.sync_serial_number,
|
||||
serialNumberSyncType: res.message.data.serial_number_sync_type,
|
||||
|
||||
syncBatch: res.message.data.sync_batches,
|
||||
batchSyncType: res.message.data.batch_sync_type,
|
||||
|
||||
syncShipment: res.message.data.sync_delivery_note,
|
||||
shipmentSyncType: res.message.data.delivery_note_sync_type,
|
||||
};
|
||||
}
|
@ -33,6 +33,7 @@ export enum IPC_ACTIONS {
|
||||
GET_TEMPLATES = 'get-templates',
|
||||
DELETE_FILE = 'delete-file',
|
||||
GET_DB_DEFAULT_PATH = 'get-db-default-path',
|
||||
SEND_API_REQUEST = 'send-api-request',
|
||||
// Database messages
|
||||
DB_CREATE = 'db-create',
|
||||
DB_CONNECT = 'db-connect',
|
||||
|
Loading…
Reference in New Issue
Block a user