Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: erpnext sync #995

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions backend/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const sqliteTypeMap: Record<string, KnexColumnType> = {
Time: 'time',
Text: 'text',
Data: 'text',
Secret: 'text',
Link: 'text',
DynamicLink: 'text',
Password: 'text',
Expand Down
42 changes: 42 additions & 0 deletions fyo/model/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -66,6 +68,8 @@ export class Doc extends Observable<DocValue | Doc[]> {
_notInserted = true;

_syncing = false;
_addDocToSyncQueue = true;

constructor(
schema: Schema,
data: DocValueMap,
Expand Down Expand Up @@ -247,6 +251,22 @@ export class Doc extends Observable<DocValue | Doc[]> {
return true;
}

get shouldDocSyncToERPNext(): boolean {
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;
Expand Down Expand Up @@ -912,6 +932,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;
}
Expand Down
10 changes: 10 additions & 0 deletions main/api.ts
Original file line number Diff line number Diff line change
@@ -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;
}[];
}
12 changes: 12 additions & 0 deletions main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
Expand Down
8 changes: 8 additions & 0 deletions main/registerIpcMainActionListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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
*/
Expand Down
17 changes: 17 additions & 0 deletions models/baseModels/ERPNextSyncQueue/ERPNextSyncQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Doc } from 'fyo/model/doc';
import { HiddenMap, ListViewSettings } from 'fyo/model/types';

export class ERPNextSyncQueue extends Doc {
referenceType?: string;
documentName?: string;

hidden: HiddenMap = {
name: () => true,
};

static getListViewSettings(): ListViewSettings {
return {
columns: ['referenceType', 'documentName'],
};
}
}
61 changes: 61 additions & 0 deletions models/baseModels/ERPNextSyncSettings/ERPNextSyncSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Doc } from 'fyo/model/doc';
import { HiddenMap } from 'fyo/model/types';

export class ERPNextSyncSettings extends Doc {
endpoint?: string;
authToken?: string;
integrationAppVersion?: string;
isEnabled?: boolean;
dataSyncInterval?: number;

syncItem?: boolean;
itemSyncType?: string;

syncCustomer?: boolean;
customerSyncType?: string;

syncSupplier?: boolean;
supplierSyncType?: string;

syncSalesInvoice?: boolean;
salesInvoiceSyncType?: string;

syncSalesInvoicePayment?: boolean;
sinvPaymentType?: string;

syncStockMovement?: boolean;
stockMovementSyncType?: string;

syncPriceList?: boolean;
priceListSyncType?: string;

syncSerialNumber?: boolean;
serialNumberSyncType?: string;

syncBatch?: boolean;
batchSyncType?: string;

syncShipment?: boolean;
shipmentSyncType?: string;

hidden: HiddenMap = {
syncPriceList: () => {
return !this.fyo.singles.AccountingSettings?.enablePriceList;
},
priceListSyncType: () => {
return !this.fyo.singles.AccountingSettings?.enablePriceList;
},
syncSerialNumber: () => {
return !this.fyo.singles.InventorySettings?.enableSerialNumber;
},
serialNumberSyncType: () => {
return !this.fyo.singles.InventorySettings?.enableSerialNumber;
},
syncBatch: () => {
return !this.fyo.singles.InventorySettings?.enableBatches;
},
batchSyncType: () => {
return !this.fyo.singles.InventorySettings?.enableBatches;
},
};
}
17 changes: 17 additions & 0 deletions models/baseModels/FetchFromERPNextQueue/FetchFromERPNextQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Doc } from 'fyo/model/doc';
import { HiddenMap, ListViewSettings } from 'fyo/model/types';

export class FetchFromERPNextQueue extends Doc {
referenceType?: string;
documentName?: string;

hidden: HiddenMap = {
name: () => true,
};

static getListViewSettings(): ListViewSettings {
return {
columns: ['referenceType', 'documentName'],
};
}
}
24 changes: 24 additions & 0 deletions models/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ import { OpeningAmounts } from './inventory/Point of Sale/OpeningAmounts';
import { OpeningCash } from './inventory/Point of Sale/OpeningCash';
import { POSSettings } from './inventory/Point of Sale/POSSettings';
import { POSShift } from './inventory/Point of Sale/POSShift';
import { ERPNextSyncSettings } from './baseModels/ERPNextSyncSettings/ERPNextSyncSettings';
import { ERPNextSyncQueue } from './baseModels/ERPNextSyncQueue/ERPNextSyncQueue';
import { FetchFromERPNextQueue } from './baseModels/FetchFromERPNextQueue/FetchFromERPNextQueue';

export const models = {
Account,
Expand Down Expand Up @@ -103,6 +106,10 @@ export const models = {
OpeningCash,
POSSettings,
POSShift,
// ERPNext Sync
ERPNextSyncSettings,
ERPNextSyncQueue,
FetchFromERPNextQueue,
} as ModelMap;

export async function getRegionalModels(
Expand Down
8 changes: 6 additions & 2 deletions models/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export enum ModelNameEnum {
GetStarted = 'GetStarted',
Defaults = 'Defaults',
Item = 'Item',
ItemPrice = 'ItemPrice',
UOM = 'UOM',
UOMConversionItem = 'UOMConversionItem',
JournalEntry = 'JournalEntry',
Expand All @@ -28,6 +27,7 @@ export enum ModelNameEnum {
Payment = 'Payment',
PaymentFor = 'PaymentFor',
PriceList = 'PriceList',
PriceListItem = 'PriceListItem',
PricingRule = 'PricingRule',
PricingRuleItem = 'PricingRuleItem',
PricingRuleDetail = 'PricingRuleDetail',
Expand Down Expand Up @@ -59,7 +59,11 @@ export enum ModelNameEnum {
CustomForm = 'CustomForm',
CustomField = 'CustomField',
POSSettings = 'POSSettings',
POSShift = 'POSShift'
POSShift = 'POSShift',

ERPNextSyncSettings= 'ERPNextSyncSettings',
ERPNextSyncQueue = 'ERPNextSyncQueue',
FetchFromERPNextQueue = 'FetchFromERPNextQueue',
}

export type ModelName = keyof typeof ModelNameEnum;
7 changes: 7 additions & 0 deletions schemas/app/AccountingSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@
"default": false,
"section": "Features"
},
{
"fieldname": "enableERPNextSync",
"label": "Enable ERPNext Sync",
"fieldtype": "Check",
"default": false,
"section": "Features"
},
{
"fieldname": "enableLead",
"label": "Enable Lead",
Expand Down
70 changes: 70 additions & 0 deletions schemas/app/ERPNextSyncQueue.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "ERPNextSyncQueue",
"label": "ERPNext Sync Queue",
"naming": "random",
"fields": [
{
"fieldname": "referenceType",
"label": "Ref. Type",
"fieldtype": "Select",
"options": [
{
"value": "Item",
"label": "Item"
},
{
"value": "Party",
"label": "Party"
},
{
"value": "SalesInvoice",
"label": "Sales Invoice"
},
{
"value": "Payment",
"label": "Sales Payment"
},
{
"value": "StockMovement",
"label": "Stock"
},
{
"value": "PriceList",
"label": "Price List"
},
{
"value": "PriceListItem",
"label": "Price List Item"
},
{
"value": "SerialNumber",
"label": "Serial Number"
},
{
"value": "Batch",
"label": "Batch"
},
{
"value": "UOM",
"label": "UOM"
},
{
"value": "Shipment",
"label": "Shipment"
},
{
"value": "Address",
"label": "Address"
}
],
"required": true
},
{
"fieldname": "documentName",
"label": "Document Name",
"fieldtype": "DynamicLink",
"references": "referenceType",
"required": true
}
]
}
Loading