mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
Merge branch 'master' into autogenerate-batch
This commit is contained in:
+24
-2
@@ -46,6 +46,7 @@ import {
|
|||||||
import { validateOptions, validateRequired } from './validationFunction';
|
import { validateOptions, validateRequired } from './validationFunction';
|
||||||
import { getShouldDocSyncToERPNext } from 'src/utils/erpnextSync';
|
import { getShouldDocSyncToERPNext } from 'src/utils/erpnextSync';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { DocItem } from 'models/inventory/types';
|
||||||
|
|
||||||
export class Doc extends Observable<DocValue | Doc[]> {
|
export class Doc extends Observable<DocValue | Doc[]> {
|
||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
@@ -920,6 +921,25 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
|
|
||||||
return this;
|
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> {
|
async sync(): Promise<Doc> {
|
||||||
this._syncing = true;
|
this._syncing = true;
|
||||||
@@ -936,10 +956,12 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
|
|
||||||
if (this._addDocToSyncQueue && !!this.shouldDocSyncToERPNext) {
|
if (this._addDocToSyncQueue && !!this.shouldDocSyncToERPNext) {
|
||||||
const isSalesInvoice = this.schemaName === ModelNameEnum.SalesInvoice;
|
const isSalesInvoice = this.schemaName === ModelNameEnum.SalesInvoice;
|
||||||
|
const hasERPSyncableItems = await this._hasERPSyncableItems();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(isSalesInvoice && this.isSyncedWithErp) ||
|
hasERPSyncableItems &&
|
||||||
(isSalesInvoice && !!this.isReturn)
|
(!(isSalesInvoice && this.isSyncedWithErp) ||
|
||||||
|
(isSalesInvoice && !!this.isReturn))
|
||||||
) {
|
) {
|
||||||
if (isSalesInvoice && !this.isReturn) {
|
if (isSalesInvoice && !this.isReturn) {
|
||||||
await this.setAndSync('isSyncedWithErp', true);
|
await this.setAndSync('isSyncedWithErp', true);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { DatabaseManager } from '../backend/database/manager';
|
||||||
|
import { ModelNameEnum } from '../models/types';
|
||||||
|
|
||||||
|
export async function checkLoyaltyProgramExpiry() {
|
||||||
|
const dm = new DatabaseManager();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentDate = new Date();
|
||||||
|
|
||||||
|
const loyaltyPrograms = await dm.db?.getAll(ModelNameEnum.LoyaltyProgram, {
|
||||||
|
fields: ['name', 'toDate', 'status', 'isEnabled', 'maximumUse', 'used'],
|
||||||
|
filters: {
|
||||||
|
status: ['not in', ['Expired']],
|
||||||
|
isEnabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loyaltyPrograms) {
|
||||||
|
for (const program of loyaltyPrograms) {
|
||||||
|
if (program.toDate && new Date(String(program.toDate)) <= currentDate) {
|
||||||
|
await dm.db?.knex!(ModelNameEnum.LoyaltyProgram)
|
||||||
|
.where({ name: program.name })
|
||||||
|
.update({
|
||||||
|
status: 'Expired',
|
||||||
|
isEnabled: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
timestamp: currentDate.toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await dm.call('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLoyaltyProgramExpiry().catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
@@ -24,6 +24,15 @@ export async function initScheduler(interval: string) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'checkLoyaltyProgramExpiry',
|
||||||
|
interval: '0 1 * * *',
|
||||||
|
worker: {
|
||||||
|
workerData: {
|
||||||
|
useTsNode: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
worker: {
|
worker: {
|
||||||
argv: ['--require', 'ts-node/register'],
|
argv: ['--require', 'ts-node/register'],
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ import {
|
|||||||
getReturnLoyaltyPoints,
|
getReturnLoyaltyPoints,
|
||||||
getItemQtyMap,
|
getItemQtyMap,
|
||||||
getItemVisibility,
|
getItemVisibility,
|
||||||
|
validateLoyaltyProgram,
|
||||||
|
getLoyaltyProgramTier,
|
||||||
|
isLoyaltyProgramExpiredAndMaxed,
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||||
import { validateBatch } from 'models/inventory/helpers';
|
import { validateBatch } from 'models/inventory/helpers';
|
||||||
@@ -212,13 +215,49 @@ export abstract class Invoice extends Transactional {
|
|||||||
this.party
|
this.party
|
||||||
)) as Party;
|
)) as Party;
|
||||||
|
|
||||||
if ((this.loyaltyPoints as number) > (partyDoc?.loyaltyPoints || 0)) {
|
if (this.redeemLoyaltyPoints && (this.loyaltyPoints as number) > 0) {
|
||||||
|
const currentPoints = partyDoc?.loyaltyPoints || 0;
|
||||||
|
|
||||||
|
let pointsToBeEarned = 0;
|
||||||
|
if (!this.isReturn && this.loyaltyProgram) {
|
||||||
|
const loyaltyProgramDoc = (await this.fyo.doc.getDoc(
|
||||||
|
ModelNameEnum.LoyaltyProgram,
|
||||||
|
this.loyaltyProgram
|
||||||
|
)) as LoyaltyProgram;
|
||||||
|
|
||||||
|
const tier = getLoyaltyProgramTier(
|
||||||
|
loyaltyProgramDoc,
|
||||||
|
this?.grandTotal as Money
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tier) {
|
||||||
|
const collectionFactor = tier.collectionFactor as number;
|
||||||
|
pointsToBeEarned =
|
||||||
|
Math.round(this?.grandTotal?.float || 0) * collectionFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalAvailablePoints = currentPoints + pointsToBeEarned;
|
||||||
|
if ((this.loyaltyPoints as number) > totalAvailablePoints) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`${
|
||||||
|
this.party as string
|
||||||
|
} only has ${currentPoints} points (${pointsToBeEarned} will be earned from this transaction)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(this.loyaltyPoints as number) > (partyDoc?.loyaltyPoints || 0)
|
||||||
|
) {
|
||||||
throw new ValidationError(
|
throw new ValidationError(
|
||||||
t`${this.party as string} only has ${
|
t`${this.party as string} only has ${
|
||||||
partyDoc.loyaltyPoints as number
|
partyDoc.loyaltyPoints as number
|
||||||
} points`
|
} points`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.loyaltyProgram) {
|
||||||
|
await validateLoyaltyProgram(this, this.loyaltyProgram);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterSubmit() {
|
async afterSubmit() {
|
||||||
@@ -275,6 +314,10 @@ export abstract class Invoice extends Transactional {
|
|||||||
if (this.schemaName === ModelNameEnum.SalesInvoice) {
|
if (this.schemaName === ModelNameEnum.SalesInvoice) {
|
||||||
this.updateUsedCountOfCoupons();
|
this.updateUsedCountOfCoupons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.loyaltyProgram) {
|
||||||
|
await this.updateUsedCountOfLoyaltyProgram();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterCancel() {
|
async afterCancel() {
|
||||||
@@ -284,6 +327,10 @@ export abstract class Invoice extends Transactional {
|
|||||||
await this._updateIsItemsReturned();
|
await this._updateIsItemsReturned();
|
||||||
await this._removeLoyaltyPointEntry();
|
await this._removeLoyaltyPointEntry();
|
||||||
this.reduceUsedCountOfCoupons();
|
this.reduceUsedCountOfCoupons();
|
||||||
|
|
||||||
|
if (this.loyaltyProgram) {
|
||||||
|
await this.reduceUsedCountOfLoyaltyProgram();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _removeLoyaltyPointEntry() {
|
async _removeLoyaltyPointEntry() {
|
||||||
@@ -838,6 +885,63 @@ export abstract class Invoice extends Transactional {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateUsedCountOfLoyaltyProgram() {
|
||||||
|
if (!this.loyaltyProgram) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loyaltyProgramDoc = await this.fyo.doc.getDoc(
|
||||||
|
ModelNameEnum.LoyaltyProgram,
|
||||||
|
this.loyaltyProgram
|
||||||
|
);
|
||||||
|
|
||||||
|
const maximumUse = loyaltyProgramDoc.maximumUse as number;
|
||||||
|
const used = (loyaltyProgramDoc.used as number) || 0;
|
||||||
|
|
||||||
|
if (this.redeemLoyaltyPoints) {
|
||||||
|
const newUsedCount = used + 1;
|
||||||
|
|
||||||
|
if (maximumUse > 0 && newUsedCount >= maximumUse) {
|
||||||
|
await loyaltyProgramDoc.setAndSync({
|
||||||
|
used: newUsedCount,
|
||||||
|
isEnabled: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await loyaltyProgramDoc.setAndSync({
|
||||||
|
used: newUsedCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reduceUsedCountOfLoyaltyProgram() {
|
||||||
|
if (!this.loyaltyProgram) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loyaltyProgramDoc = await this.fyo.doc.getDoc(
|
||||||
|
ModelNameEnum.LoyaltyProgram,
|
||||||
|
this.loyaltyProgram
|
||||||
|
);
|
||||||
|
|
||||||
|
const maximumUse = loyaltyProgramDoc.maximumUse as number;
|
||||||
|
const used = (loyaltyProgramDoc.used as number) || 0;
|
||||||
|
const newUsedCount = used - 1;
|
||||||
|
|
||||||
|
if (this.redeemLoyaltyPoints) {
|
||||||
|
if (newUsedCount < maximumUse) {
|
||||||
|
await loyaltyProgramDoc.setAndSync({
|
||||||
|
used: newUsedCount,
|
||||||
|
isEnabled: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await loyaltyProgramDoc.setAndSync({
|
||||||
|
used: newUsedCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateIsItemsFullyReturned(doc?: Invoice) {
|
async updateIsItemsFullyReturned(doc?: Invoice) {
|
||||||
if (!doc?.returnAgainst || doc.schemaName !== ModelNameEnum.SalesInvoice) {
|
if (!doc?.returnAgainst || doc.schemaName !== ModelNameEnum.SalesInvoice) {
|
||||||
return;
|
return;
|
||||||
@@ -898,11 +1002,27 @@ export abstract class Invoice extends Transactional {
|
|||||||
this.loyaltyProgram
|
this.loyaltyProgram
|
||||||
)) as LoyaltyProgram;
|
)) as LoyaltyProgram;
|
||||||
|
|
||||||
const expiryDate = this.date as Date;
|
const invoiceDate = this.date as Date;
|
||||||
const fromDate = loyaltyProgramDoc.fromDate as Date;
|
const fromDate = loyaltyProgramDoc.fromDate as Date;
|
||||||
const toDate = loyaltyProgramDoc.toDate as Date;
|
const toDate = loyaltyProgramDoc.toDate as Date;
|
||||||
|
|
||||||
if (fromDate <= expiryDate && toDate >= expiryDate) {
|
const normalizedInvoiceDate = new Date(invoiceDate);
|
||||||
|
normalizedInvoiceDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const normalizedFromDate = new Date(fromDate);
|
||||||
|
normalizedFromDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const normalizedToDate = new Date(toDate);
|
||||||
|
normalizedToDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (normalizedToDate.getTime() < normalizedInvoiceDate.getTime()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
normalizedInvoiceDate.getTime() >= normalizedFromDate.getTime() &&
|
||||||
|
normalizedInvoiceDate.getTime() <= normalizedToDate.getTime()
|
||||||
|
) {
|
||||||
const party = (await this.loadAndGetLink('party')) as Party;
|
const party = (await this.loadAndGetLink('party')) as Party;
|
||||||
|
|
||||||
await createLoyaltyPointEntry(this);
|
await createLoyaltyPointEntry(this);
|
||||||
@@ -989,7 +1109,13 @@ export abstract class Invoice extends Transactional {
|
|||||||
ModelNameEnum.Party,
|
ModelNameEnum.Party,
|
||||||
this.party
|
this.party
|
||||||
);
|
);
|
||||||
return partyDoc?.loyaltyProgram as string;
|
const loyaltyProgramName = partyDoc?.loyaltyProgram as string;
|
||||||
|
|
||||||
|
if (!loyaltyProgramName) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return loyaltyProgramName;
|
||||||
},
|
},
|
||||||
dependsOn: ['party', 'name'],
|
dependsOn: ['party', 'name'],
|
||||||
},
|
},
|
||||||
@@ -999,6 +1125,17 @@ export abstract class Invoice extends Transactional {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loyaltyProgramName = this.loyaltyProgram as string;
|
||||||
|
if (loyaltyProgramName) {
|
||||||
|
const isExpiredAndMaxed = await isLoyaltyProgramExpiredAndMaxed(
|
||||||
|
this.fyo,
|
||||||
|
loyaltyProgramName
|
||||||
|
);
|
||||||
|
if (isExpiredAndMaxed) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loyaltyPoints = await this.fyo.getValue(
|
const loyaltyPoints = await this.fyo.getValue(
|
||||||
ModelNameEnum.Party,
|
ModelNameEnum.Party,
|
||||||
this.party,
|
this.party,
|
||||||
@@ -1006,7 +1143,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
);
|
);
|
||||||
return loyaltyPoints || 0;
|
return loyaltyPoints || 0;
|
||||||
},
|
},
|
||||||
dependsOn: ['party'],
|
dependsOn: ['party', 'loyaltyProgram'],
|
||||||
},
|
},
|
||||||
currency: {
|
currency: {
|
||||||
formula: async () => {
|
formula: async () => {
|
||||||
@@ -1199,7 +1336,13 @@ export abstract class Invoice extends Transactional {
|
|||||||
loyaltyProgram: () => !this.loyaltyProgram,
|
loyaltyProgram: () => !this.loyaltyProgram,
|
||||||
availableLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
|
availableLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
|
||||||
loyaltyPoints: () => !this.redeemLoyaltyPoints || this.isReturn,
|
loyaltyPoints: () => !this.redeemLoyaltyPoints || this.isReturn,
|
||||||
redeemLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
|
redeemLoyaltyPoints: () => {
|
||||||
|
if (!this.loyaltyProgram || this.isReturn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this.availableLoyaltyPoints ?? 0) <= 0;
|
||||||
|
},
|
||||||
coupons: () => this.isSubmitted && !this.coupons?.length,
|
coupons: () => this.isSubmitted && !this.coupons?.length,
|
||||||
priceList: () =>
|
priceList: () =>
|
||||||
!this.fyo.singles.AccountingSettings?.enablePriceList ||
|
!this.fyo.singles.AccountingSettings?.enablePriceList ||
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ import { Item } from '../Item/Item';
|
|||||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||||
import { isPesa } from 'fyo/utils';
|
import { isPesa } from 'fyo/utils';
|
||||||
import { PricingRule } from '../PricingRule/PricingRule';
|
import { PricingRule } from '../PricingRule/PricingRule';
|
||||||
import { getItemRateFromPriceList, getPricingRule } from 'models/helpers';
|
import {
|
||||||
|
getItemRateFromPriceList,
|
||||||
|
getPricingRule,
|
||||||
|
getItemVisibility,
|
||||||
|
} from 'models/helpers';
|
||||||
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
||||||
import { getSuggestedBatchName } from 'models/inventory/helpers';
|
import { getSuggestedBatchName } from 'models/inventory/helpers';
|
||||||
import { ValuationMethod } from 'models/inventory/types';
|
import { ValuationMethod } from 'models/inventory/types';
|
||||||
@@ -29,6 +33,7 @@ import {
|
|||||||
getStockLedgerEntries,
|
getStockLedgerEntries,
|
||||||
getStockBalanceEntries,
|
getStockBalanceEntries,
|
||||||
} from 'reports/inventory/helpers';
|
} from 'reports/inventory/helpers';
|
||||||
|
import { QueryFilter } from 'utils/db/types';
|
||||||
|
|
||||||
export abstract class InvoiceItem extends Doc {
|
export abstract class InvoiceItem extends Doc {
|
||||||
item?: string;
|
item?: string;
|
||||||
@@ -775,13 +780,33 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
item: (doc: Doc) => {
|
item: async (doc: Doc): Promise<QueryFilter> => {
|
||||||
let itemNotFor = 'Sales';
|
let itemNotFor = 'Sales';
|
||||||
if (doc.isSales) {
|
if (doc.isSales) {
|
||||||
itemNotFor = 'Purchases';
|
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) => {
|
batch: async (doc: Doc) => {
|
||||||
const hasBatch = !!(await doc.fyo.getValue(
|
const hasBatch = !!(await doc.fyo.getValue(
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export class Item extends Doc {
|
|||||||
hsnCode?: number;
|
hsnCode?: number;
|
||||||
hasSerialNumber?: boolean;
|
hasSerialNumber?: boolean;
|
||||||
serialNumberSeries?: string;
|
serialNumberSeries?: string;
|
||||||
|
datafromErp?: boolean;
|
||||||
uomConversions: UOMConversionItem[] = [];
|
uomConversions: UOMConversionItem[] = [];
|
||||||
|
|
||||||
formulas: FormulaMap = {
|
formulas: FormulaMap = {
|
||||||
|
|||||||
@@ -1,22 +1,42 @@
|
|||||||
|
import { DocValue } from 'fyo/core/types';
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { FiltersMap, ListViewSettings } from 'fyo/model/types';
|
import { ListViewSettings, ValidationMap } from 'fyo/model/types';
|
||||||
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
import { CollectionRulesItems } from '../CollectionRulesItems/CollectionRulesItems';
|
import { CollectionRulesItems } from '../CollectionRulesItems/CollectionRulesItems';
|
||||||
import { AccountRootTypeEnum } from '../Account/types';
|
import { getLoyaltyProgramStatusColumn } from '../../helpers';
|
||||||
|
|
||||||
export class LoyaltyProgram extends Doc {
|
export class LoyaltyProgram extends Doc {
|
||||||
collectionRules?: CollectionRulesItems[];
|
collectionRules?: CollectionRulesItems[];
|
||||||
expiryDuration?: number;
|
expiryDuration?: number;
|
||||||
|
maximumUse?: number;
|
||||||
|
used?: number;
|
||||||
|
status?: 'Active' | 'Expired' | 'Disabled' | 'Maxed';
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
validations: ValidationMap = {
|
||||||
expenseAccount: () => ({
|
used: (value: DocValue) => {
|
||||||
rootType: AccountRootTypeEnum.Expense,
|
const used = value as number;
|
||||||
isGroup: false,
|
const maximumUse = this.maximumUse as number;
|
||||||
}),
|
|
||||||
|
if (used < 0) {
|
||||||
|
throw new ValidationError('Used count cannot be negative');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maximumUse > 0 && used > maximumUse) {
|
||||||
|
throw new ValidationError('Used count cannot exceed maximum use limit');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
maximumUse: (value: DocValue) => {
|
||||||
|
const maximumUse = value as number;
|
||||||
|
|
||||||
|
if (maximumUse < 0) {
|
||||||
|
throw new ValidationError('Maximum use cannot be negative');
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
columns: ['name', 'fromDate', 'toDate', 'expiryDuration'],
|
columns: ['name', getLoyaltyProgramStatusColumn(), 'fromDate', 'toDate'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { PartyRole } from './types';
|
import { PartyRole } from './types';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { isLoyaltyProgramExpiredAndMaxed } from 'models/helpers';
|
||||||
|
|
||||||
export class Party extends Doc {
|
export class Party extends Doc {
|
||||||
role?: PartyRole;
|
role?: PartyRole;
|
||||||
@@ -66,6 +67,17 @@ export class Party extends Doc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _getTotalLoyaltyPoints() {
|
async _getTotalLoyaltyPoints() {
|
||||||
|
const loyaltyProgramName = this.loyaltyProgram as string;
|
||||||
|
if (loyaltyProgramName) {
|
||||||
|
const isExpiredAndMaxed = await isLoyaltyProgramExpiredAndMaxed(
|
||||||
|
this.fyo,
|
||||||
|
loyaltyProgramName
|
||||||
|
);
|
||||||
|
if (isExpiredAndMaxed) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const data = (await this.fyo.db.getAll(ModelNameEnum.LoyaltyPointEntry, {
|
const data = (await this.fyo.db.getAll(ModelNameEnum.LoyaltyPointEntry, {
|
||||||
fields: ['name', 'loyaltyPoints', 'expiryDate', 'postingDate'],
|
fields: ['name', 'loyaltyPoints', 'expiryDate', 'postingDate'],
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@@ -113,6 +113,15 @@ export class SalesInvoice extends Invoice {
|
|||||||
ModelNameEnum.LoyaltyProgram,
|
ModelNameEnum.LoyaltyProgram,
|
||||||
this.loyaltyProgram
|
this.loyaltyProgram
|
||||||
)) as LoyaltyProgram;
|
)) as LoyaltyProgram;
|
||||||
|
const toDate = loyaltyProgramDoc?.toDate as Date;
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (toDate && new Date(toDate).getTime() < today.getTime()) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`Loyalty program has expired and cannot be applied`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this?.grandTotal) {
|
if (!this?.grandTotal) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -27,10 +27,17 @@ const partyData = {
|
|||||||
email: 'john@whoe.com',
|
email: 'john@whoe.com',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const fromDate = new Date(today);
|
||||||
|
fromDate.setDate(today.getDate() - 10);
|
||||||
|
|
||||||
|
const toDate = new Date(today);
|
||||||
|
toDate.setDate(today.getDate() + 20);
|
||||||
|
|
||||||
const loyaltyProgramData = {
|
const loyaltyProgramData = {
|
||||||
name: 'program',
|
name: 'program',
|
||||||
fromDate: new Date('12/10/2024'),
|
fromDate: fromDate,
|
||||||
toDate: new Date('12/30/2024'),
|
toDate: toDate,
|
||||||
email: 'sample@gmail.com',
|
email: 'sample@gmail.com',
|
||||||
mobile: '1234567890',
|
mobile: '1234567890',
|
||||||
expenseAccount: accountData.name,
|
expenseAccount: accountData.name,
|
||||||
@@ -118,11 +125,11 @@ async function loyaltyPointEntryDoc(sinvName: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createSalesInvoice() {
|
async function createSalesInvoice(invoiceDate?: Date) {
|
||||||
const sinvDoc = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
const sinvDoc = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
account: 'Debtors',
|
account: 'Debtors',
|
||||||
party: partyData.name,
|
party: partyData.name,
|
||||||
date: new Date('12/11/2024'),
|
date: invoiceDate || new Date(),
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
item: itemData.name,
|
item: itemData.name,
|
||||||
@@ -189,10 +196,10 @@ test('create Sales Invoice and verify loyalty points are created correctly', asy
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('create SINV with future date and verify loyalty points are not created', async (t) => {
|
test('create SINV with future date and verify loyalty points are not created', async (t) => {
|
||||||
const futureDate = new Date(new Date().setDate(new Date().getDate() + 20));
|
const futureDate = new Date();
|
||||||
|
futureDate.setDate(futureDate.getDate() + 30);
|
||||||
|
|
||||||
const sinvDoc = await createSalesInvoice();
|
const sinvDoc = await createSalesInvoice(futureDate);
|
||||||
sinvDoc.date = futureDate;
|
|
||||||
|
|
||||||
await sinvDoc.sync();
|
await sinvDoc.sync();
|
||||||
await sinvDoc.submit();
|
await sinvDoc.submit();
|
||||||
|
|||||||
+174
-9
@@ -119,6 +119,11 @@ export async function getItemQtyMap(doc: SalesInvoice): Promise<ItemQtyMap> {
|
|||||||
|
|
||||||
export async function getItemVisibility(fyo: Fyo): Promise<ItemVisibility> {
|
export async function getItemVisibility(fyo: Fyo): Promise<ItemVisibility> {
|
||||||
const posProfileName = fyo.singles.POSSettings?.posProfile as string;
|
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) {
|
if (posProfileName) {
|
||||||
const posProfile = await fyo.doc.getDoc(
|
const posProfile = await fyo.doc.getDoc(
|
||||||
@@ -741,6 +746,74 @@ export function getDocStatusListColumn(): ColumnConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLoyaltyProgramStatusColumn(): ColumnConfig {
|
||||||
|
return {
|
||||||
|
label: t`Status`,
|
||||||
|
fieldname: 'status',
|
||||||
|
fieldtype: 'Select',
|
||||||
|
render(doc) {
|
||||||
|
const status = getLoyaltyProgramStatus(doc);
|
||||||
|
const color = loyaltyProgramStatusColor[status] ?? 'gray';
|
||||||
|
const label = getLoyaltyProgramStatusText(status);
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||||
|
metadata: {
|
||||||
|
status,
|
||||||
|
color,
|
||||||
|
label,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLoyaltyProgramStatus(doc?: RenderData | Doc): string {
|
||||||
|
if (!doc) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDate = new Date();
|
||||||
|
currentDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const toDate = doc.toDate as Date;
|
||||||
|
|
||||||
|
if (toDate && toDate <= currentDate) {
|
||||||
|
return 'Expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
const maximumUse = doc.maximumUse as number;
|
||||||
|
const used = doc.used as number;
|
||||||
|
|
||||||
|
if (maximumUse > 0 && used >= maximumUse) {
|
||||||
|
return 'Maxed';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Active';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loyaltyProgramStatusColor: Record<string, string | undefined> = {
|
||||||
|
Active: 'green',
|
||||||
|
Disabled: 'gray',
|
||||||
|
Expired: 'red',
|
||||||
|
Maxed: 'orange',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getLoyaltyProgramStatusText(status: string): string {
|
||||||
|
switch (status) {
|
||||||
|
case 'Active':
|
||||||
|
return t`Active`;
|
||||||
|
case 'Disabled':
|
||||||
|
return t`Disabled`;
|
||||||
|
case 'Expired':
|
||||||
|
return t`Expired`;
|
||||||
|
case 'Maxed':
|
||||||
|
return t`Maxed`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ModelsWithItems = Invoice | StockTransfer | StockMovement;
|
type ModelsWithItems = Invoice | StockTransfer | StockMovement;
|
||||||
export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
|
export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
|
||||||
if (!doc.canEdit) {
|
if (!doc.canEdit) {
|
||||||
@@ -893,6 +966,15 @@ export async function createLoyaltyPointEntry(doc: Invoice) {
|
|||||||
if (!loyaltyProgramDoc.isEnabled) {
|
if (!loyaltyProgramDoc.isEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toDate = loyaltyProgramDoc.toDate as Date;
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (toDate && new Date(toDate).getTime() < today.getTime()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const expiryDate = new Date(Date.now());
|
const expiryDate = new Date(Date.now());
|
||||||
|
|
||||||
expiryDate.setDate(
|
expiryDate.setDate(
|
||||||
@@ -961,18 +1043,22 @@ export function getLoyaltyProgramTier(
|
|||||||
let loyaltyProgramTier: CollectionRulesItems | undefined;
|
let loyaltyProgramTier: CollectionRulesItems | undefined;
|
||||||
|
|
||||||
for (const row of loyaltyProgramData.collectionRules) {
|
for (const row of loyaltyProgramData.collectionRules) {
|
||||||
if (isPesa(row.minimumTotalSpent)) {
|
if (row.minimumTotalSpent !== undefined && row.minimumTotalSpent !== null) {
|
||||||
const minimumSpent = row.minimumTotalSpent;
|
let minimumSpent: Money;
|
||||||
|
|
||||||
if (!minimumSpent.lte(grandTotal)) {
|
if (isPesa(row.minimumTotalSpent)) {
|
||||||
continue;
|
minimumSpent = row.minimumTotalSpent;
|
||||||
|
} else {
|
||||||
|
minimumSpent = new Money(row.minimumTotalSpent as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (minimumSpent.lte(grandTotal)) {
|
||||||
!loyaltyProgramTier ||
|
if (
|
||||||
minimumSpent.gt(loyaltyProgramTier.minimumTotalSpent as Money)
|
!loyaltyProgramTier ||
|
||||||
) {
|
minimumSpent.gt(loyaltyProgramTier.minimumTotalSpent as Money)
|
||||||
loyaltyProgramTier = row;
|
) {
|
||||||
|
loyaltyProgramTier = row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1547,6 +1633,52 @@ export async function validateCouponCode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function validateLoyaltyProgram(
|
||||||
|
doc: Invoice,
|
||||||
|
loyaltyProgramName: string
|
||||||
|
) {
|
||||||
|
const loyaltyProgram = await doc.fyo.db.getAll(ModelNameEnum.LoyaltyProgram, {
|
||||||
|
fields: ['fromDate', 'toDate', 'maximumUse', 'used', 'isEnabled'],
|
||||||
|
filters: { name: loyaltyProgramName },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!loyaltyProgram[0]?.isEnabled) {
|
||||||
|
throw new ValidationError(
|
||||||
|
'Loyalty program cannot be applied as it is not enabled'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(loyaltyProgram[0]?.maximumUse as number) > 0 &&
|
||||||
|
(loyaltyProgram[0]?.used as number) >=
|
||||||
|
(loyaltyProgram[0]?.maximumUse as number)
|
||||||
|
) {
|
||||||
|
throw new ValidationError(
|
||||||
|
'Loyalty program has reached maximum usage limit'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
loyaltyProgram[0].fromDate &&
|
||||||
|
(doc.date as Date) < (loyaltyProgram[0].fromDate as Date)
|
||||||
|
) {
|
||||||
|
throw new ValidationError('Loyalty program is not yet active');
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDate = loyaltyProgram[0].toDate as Date;
|
||||||
|
if (toDate) {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
const normalizedToDate = new Date(toDate);
|
||||||
|
normalizedToDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Only throw error if toDate is clearly in the past
|
||||||
|
if (normalizedToDate.getTime() < today.getTime()) {
|
||||||
|
throw new ValidationError('Loyalty program has expired');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function removeFreeItems(sinvDoc: SalesInvoice) {
|
export function removeFreeItems(sinvDoc: SalesInvoice) {
|
||||||
if (!sinvDoc || !sinvDoc.items) {
|
if (!sinvDoc || !sinvDoc.items) {
|
||||||
return;
|
return;
|
||||||
@@ -1621,3 +1753,36 @@ export function roundFreeItemQty(
|
|||||||
): number {
|
): number {
|
||||||
return Math[roundingMethod](quantity);
|
return Math[roundingMethod](quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function isLoyaltyProgramExpiredAndMaxed(
|
||||||
|
fyo: Fyo,
|
||||||
|
loyaltyProgramName: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (!loyaltyProgramName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loyaltyProgram = await fyo.db.getAll(ModelNameEnum.LoyaltyProgram, {
|
||||||
|
fields: ['toDate', 'maximumUse', 'used', 'isEnabled'],
|
||||||
|
filters: { name: loyaltyProgramName },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!loyaltyProgram.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const program = loyaltyProgram[0];
|
||||||
|
const currentDate = new Date();
|
||||||
|
currentDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const toDate = program.toDate as Date;
|
||||||
|
const isExpired =
|
||||||
|
toDate && new Date(toDate).getTime() < currentDate.getTime();
|
||||||
|
|
||||||
|
const maximumUse = (program.maximumUse as number) || 0;
|
||||||
|
const used = (program.used as number) || 0;
|
||||||
|
const isMaxed = maximumUse > 0 && used >= maximumUse;
|
||||||
|
|
||||||
|
const result = isExpired || isMaxed;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export class POSSettings extends Doc {
|
|||||||
itemWeightDigits?: number;
|
itemWeightDigits?: number;
|
||||||
defaultAccount?: string;
|
defaultAccount?: string;
|
||||||
itemVisibility?: string;
|
itemVisibility?: string;
|
||||||
|
itemVisibilityERP?: 'ERP Sync Items';
|
||||||
posUI?: 'Classic' | 'Modern';
|
posUI?: 'Classic' | 'Modern';
|
||||||
canChangeRate?: boolean;
|
canChangeRate?: boolean;
|
||||||
canEditDiscount?: boolean;
|
canEditDiscount?: boolean;
|
||||||
@@ -46,6 +47,9 @@ export class POSSettings extends Doc {
|
|||||||
!this.fyo.singles.InventorySettings?.enableBarcodes ||
|
!this.fyo.singles.InventorySettings?.enableBarcodes ||
|
||||||
!this.weightEnabledBarcode,
|
!this.weightEnabledBarcode,
|
||||||
itemVisibility: () =>
|
itemVisibility: () =>
|
||||||
!this.fyo.singles.AccountingSettings?.enablePointOfSaleWithOutInventory,
|
!this.fyo.singles.AccountingSettings?.enablePointOfSaleWithOutInventory ||
|
||||||
|
!!this.fyo.singles.AccountingSettings?.enableERPNextSync,
|
||||||
|
itemVisibilityERP: () =>
|
||||||
|
!this.fyo.singles.AccountingSettings?.enableERPNextSync,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,6 +178,14 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"target": "UOMConversionItem",
|
"target": "UOMConversionItem",
|
||||||
"section": "Inventory"
|
"section": "Inventory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "datafromErp",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": true,
|
||||||
|
"default": false,
|
||||||
|
"section": "Default",
|
||||||
|
"readOnly": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
"label": "Expiry Duration",
|
"label": "Expiry Duration",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"default": 1,
|
"default": 1,
|
||||||
"required": true
|
"hidden": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "expenseAccount",
|
"fieldname": "expenseAccount",
|
||||||
@@ -59,6 +59,21 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "Account",
|
"target": "Account",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "maximumUse",
|
||||||
|
"label": "Maximum Use",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"default": 0,
|
||||||
|
"section": "Validity and Usage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "used",
|
||||||
|
"label": "Used",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"default": 0,
|
||||||
|
"readOnly": true,
|
||||||
|
"section": "Validity and Usage"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
@@ -67,7 +82,8 @@
|
|||||||
"toDate",
|
"toDate",
|
||||||
"conversionFactor",
|
"conversionFactor",
|
||||||
"expenseAccount",
|
"expenseAccount",
|
||||||
"expiryDuration"
|
"maximumUse",
|
||||||
|
"used"
|
||||||
],
|
],
|
||||||
"keywordFields": ["name"]
|
"keywordFields": ["name"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "LoyaltyProgram",
|
"target": "LoyaltyProgram",
|
||||||
"label": "Loyalty Program",
|
"label": "Loyalty Program",
|
||||||
"section": "References",
|
"section": "Loyalty Points Redemption",
|
||||||
"readOnly": true
|
"readOnly": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Available Loyalty Points",
|
"label": "Available Loyalty Points",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"section": "References"
|
"section": "Loyalty Points Redemption"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "redeemLoyaltyPoints",
|
"fieldname": "redeemLoyaltyPoints",
|
||||||
|
|||||||
@@ -96,13 +96,6 @@
|
|||||||
"default": 0,
|
"default": 0,
|
||||||
"section": "Barcode"
|
"section": "Barcode"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "itemWeightDigits",
|
|
||||||
"label": "item Weight Digits",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"default": 0,
|
|
||||||
"section": "Barcode"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "itemVisibility",
|
"fieldname": "itemVisibility",
|
||||||
"label": "Item Visibility",
|
"label": "Item Visibility",
|
||||||
@@ -118,7 +111,27 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default": "Inventory Items",
|
"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"
|
"section": "Default"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
|
v-if="itemVisibility !== 'ERP Sync Items'"
|
||||||
class="
|
class="
|
||||||
absolute
|
absolute
|
||||||
top-1
|
top-1
|
||||||
@@ -111,6 +112,10 @@ export default defineComponent({
|
|||||||
itemQtyMap: {
|
itemQtyMap: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
itemVisibility: {
|
||||||
|
type: String,
|
||||||
|
default: 'Inventory Items',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getExtractedWords(item: string) {
|
getExtractedWords(item: string) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
size="large"
|
size="large"
|
||||||
class=""
|
class=""
|
||||||
:df="df"
|
:df="df"
|
||||||
:value="row[df.fieldname]"
|
:value="(row as POSItem)[df.fieldname as keyof POSItem]"
|
||||||
:readOnly="true"
|
:readOnly="true"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -81,13 +81,20 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
items: Array,
|
items: Array,
|
||||||
itemQtyMap: Object,
|
itemQtyMap: Object,
|
||||||
|
itemVisibility: {
|
||||||
|
type: String,
|
||||||
|
default: 'Inventory Items',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
ratio() {
|
ratio() {
|
||||||
|
if (this.itemVisibility === 'ERP Sync Items') {
|
||||||
|
return [1, 1.5, 0.8];
|
||||||
|
}
|
||||||
return [1, 1, 1, 0.7];
|
return [1, 1, 1, 0.7];
|
||||||
},
|
},
|
||||||
tableFields() {
|
tableFields() {
|
||||||
return [
|
const fields = [
|
||||||
{
|
{
|
||||||
fieldname: 'name',
|
fieldname: 'name',
|
||||||
fieldtype: 'Data',
|
fieldtype: 'Data',
|
||||||
@@ -102,13 +109,6 @@ export default defineComponent({
|
|||||||
fieldtype: 'Currency',
|
fieldtype: 'Currency',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
fieldname: 'availableQty',
|
|
||||||
label: 'Qty',
|
|
||||||
placeholder: 'Available Qty',
|
|
||||||
fieldtype: 'Float',
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
fieldname: 'unit',
|
fieldname: 'unit',
|
||||||
label: 'Unit',
|
label: 'Unit',
|
||||||
@@ -118,6 +118,18 @@ export default defineComponent({
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
] as Field[];
|
] 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: {
|
methods: {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
|
v-if="itemVisibility !== 'ERP Sync Items'"
|
||||||
class="
|
class="
|
||||||
w-6
|
w-6
|
||||||
h-6
|
h-6
|
||||||
@@ -114,6 +115,10 @@ export default defineComponent({
|
|||||||
itemQtyMap: {
|
itemQtyMap: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
itemVisibility: {
|
||||||
|
type: String,
|
||||||
|
default: 'Inventory Items',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getExtractedWords(item: string) {
|
getExtractedWords(item: string) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
:key="df.fieldname"
|
:key="df.fieldname"
|
||||||
size="large"
|
size="large"
|
||||||
:df="df"
|
:df="df"
|
||||||
:value="row[df.fieldname]"
|
:value="(row as POSItem)[df.fieldname as keyof POSItem]"
|
||||||
:readOnly="true"
|
:readOnly="true"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
:key="df.fieldname"
|
:key="df.fieldname"
|
||||||
size="large"
|
size="large"
|
||||||
:df="df"
|
:df="df"
|
||||||
:value="row[df.fieldname]"
|
:value="(row as POSItem)[df.fieldname as keyof POSItem]"
|
||||||
:readOnly="true"
|
:readOnly="true"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -137,13 +137,20 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
items: Array,
|
items: Array,
|
||||||
itemQtyMap: Object,
|
itemQtyMap: Object,
|
||||||
|
itemVisibility: {
|
||||||
|
type: String,
|
||||||
|
default: 'Inventory Items',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
ratio() {
|
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() {
|
tableFields() {
|
||||||
return [
|
const fields = [
|
||||||
{
|
{
|
||||||
fieldname: 'name',
|
fieldname: 'name',
|
||||||
fieldtype: 'Data',
|
fieldtype: 'Data',
|
||||||
@@ -158,13 +165,6 @@ export default defineComponent({
|
|||||||
fieldtype: 'Currency',
|
fieldtype: 'Currency',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
fieldname: 'availableQty',
|
|
||||||
label: t`Qty`,
|
|
||||||
placeholder: 'Available Qty',
|
|
||||||
fieldtype: 'Float',
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
fieldname: 'unit',
|
fieldname: 'unit',
|
||||||
label: t`Unit`,
|
label: t`Unit`,
|
||||||
@@ -174,6 +174,18 @@ export default defineComponent({
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
] as Field[];
|
] 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() {
|
firstColumnItems() {
|
||||||
return this.items?.slice(0, Math.ceil(this.items.length / 2));
|
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 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 = [
|
export const modalNames = [
|
||||||
'Keyboard',
|
'Keyboard',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Doc } from 'fyo/model/doc';
|
|||||||
import { isPesa } from 'fyo/utils';
|
import { isPesa } from 'fyo/utils';
|
||||||
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
||||||
import { Party } from 'models/baseModels/Party/Party';
|
import { Party } from 'models/baseModels/Party/Party';
|
||||||
|
import { LoyaltyProgram } from 'models/baseModels/LoyaltyProgram/LoyaltyProgram';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { getBgTextColorClass } from 'src/utils/colors';
|
import { getBgTextColorClass } from 'src/utils/colors';
|
||||||
@@ -77,6 +78,9 @@ export default defineComponent({
|
|||||||
ReturnIssued: this.t`Return Issued`,
|
ReturnIssued: this.t`Return Issued`,
|
||||||
Unpaid: this.t`Unpaid`,
|
Unpaid: this.t`Unpaid`,
|
||||||
PartlyPaid: this.t`Partly Paid`,
|
PartlyPaid: this.t`Partly Paid`,
|
||||||
|
Expired: this.t`Expired`,
|
||||||
|
Active: this.t`Active`,
|
||||||
|
Maxed: this.t`Maxed`,
|
||||||
}[this.status];
|
}[this.status];
|
||||||
},
|
},
|
||||||
color(): UIColors {
|
color(): UIColors {
|
||||||
@@ -99,6 +103,9 @@ const statusColorMap: Record<Status, UIColors> = {
|
|||||||
ReturnIssued: 'gray',
|
ReturnIssued: 'gray',
|
||||||
Unpaid: 'red',
|
Unpaid: 'red',
|
||||||
PartlyPaid: 'yellow',
|
PartlyPaid: 'yellow',
|
||||||
|
Expired: 'red',
|
||||||
|
Active: 'green',
|
||||||
|
Maxed: 'orange',
|
||||||
};
|
};
|
||||||
|
|
||||||
function getStatus(doc: Doc) {
|
function getStatus(doc: Doc) {
|
||||||
@@ -110,6 +117,27 @@ function getStatus(doc: Doc) {
|
|||||||
return 'NotSaved';
|
return 'NotSaved';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (doc instanceof LoyaltyProgram) {
|
||||||
|
const currentDate = new Date();
|
||||||
|
currentDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const maximumUse = doc.maximumUse as number;
|
||||||
|
const used = doc.used as number;
|
||||||
|
|
||||||
|
if (maximumUse > 0 && used >= maximumUse) {
|
||||||
|
return 'Maxed';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.toDate && doc.toDate instanceof Date) {
|
||||||
|
const toDate = new Date(doc.toDate);
|
||||||
|
toDate.setHours(0, 0, 0, 0);
|
||||||
|
if (toDate <= currentDate) {
|
||||||
|
return 'Expired';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'Active';
|
||||||
|
}
|
||||||
|
|
||||||
if (doc instanceof Party && doc.outstandingAmount?.isZero() !== true) {
|
if (doc instanceof Party && doc.outstandingAmount?.isZero() !== true) {
|
||||||
return 'Outstanding';
|
return 'Outstanding';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@
|
|||||||
v-if="tableView"
|
v-if="tableView"
|
||||||
:items="items"
|
:items="items"
|
||||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||||
|
:item-visibility="itemVisibility"
|
||||||
@add-item="(item) => emitEvent('addItem', item)"
|
@add-item="(item) => emitEvent('addItem', item)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -141,6 +142,7 @@
|
|||||||
v-else
|
v-else
|
||||||
:items="items"
|
:items="items"
|
||||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||||
|
:item-visibility="itemVisibility"
|
||||||
@add-item="(item) => emitEvent('addItem', item)"
|
@add-item="(item) => emitEvent('addItem', item)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -487,6 +489,10 @@ export default defineComponent({
|
|||||||
type: Array as PropType<POSItem[] | undefined>,
|
type: Array as PropType<POSItem[] | undefined>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
itemVisibility: {
|
||||||
|
type: String,
|
||||||
|
default: 'Inventory Items',
|
||||||
|
},
|
||||||
profile: {
|
profile: {
|
||||||
type: Object as PropType<POSProfile>,
|
type: Object as PropType<POSProfile>,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -128,6 +128,22 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loyaltyProgramDoc = await this.fyo.db.getAll(
|
||||||
|
ModelNameEnum.LoyaltyProgram,
|
||||||
|
{
|
||||||
|
fields: ['conversionFactor', 'toDate'],
|
||||||
|
filters: { name: partyData.loyaltyProgram as string },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const toDate = loyaltyProgramDoc[0]?.toDate as Date;
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (toDate && new Date(toDate).getTime() < today.getTime()) {
|
||||||
|
throw new Error(t`Loyalty program has expired and cannot be applied`);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.loyaltyPoints >= newValue) {
|
if (this.loyaltyPoints >= newValue) {
|
||||||
this.sinvDoc.loyaltyPoints = newValue;
|
this.sinvDoc.loyaltyPoints = newValue;
|
||||||
} else {
|
} else {
|
||||||
@@ -138,14 +154,6 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loyaltyProgramDoc = await this.fyo.db.getAll(
|
|
||||||
ModelNameEnum.LoyaltyProgram,
|
|
||||||
{
|
|
||||||
fields: ['conversionFactor'],
|
|
||||||
filters: { name: partyData.loyaltyProgram as string },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const loyaltyPoint =
|
const loyaltyPoint =
|
||||||
newValue * ((loyaltyProgramDoc[0]?.conversionFactor as number) || 0);
|
newValue * ((loyaltyProgramDoc[0]?.conversionFactor as number) || 0);
|
||||||
|
|
||||||
|
|||||||
@@ -349,6 +349,7 @@
|
|||||||
v-if="tableView"
|
v-if="tableView"
|
||||||
:items="items"
|
:items="items"
|
||||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||||
|
:item-visibility="itemVisibility"
|
||||||
@add-item="(item:string) => emitEvent('addItem', item)"
|
@add-item="(item:string) => emitEvent('addItem', item)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -356,6 +357,7 @@
|
|||||||
v-else
|
v-else
|
||||||
:items="items"
|
:items="items"
|
||||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||||
|
:item-visibility="itemVisibility"
|
||||||
@add-item="(item:string) => emitEvent('addItem', item)"
|
@add-item="(item:string) => emitEvent('addItem', item)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -493,6 +495,10 @@ export default defineComponent({
|
|||||||
type: Array as PropType<POSItem[] | undefined>,
|
type: Array as PropType<POSItem[] | undefined>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
itemVisibility: {
|
||||||
|
type: String,
|
||||||
|
default: 'Inventory Items',
|
||||||
|
},
|
||||||
profile: {
|
profile: {
|
||||||
type: Object as PropType<POSProfile>,
|
type: Object as PropType<POSProfile>,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
+30
-3
@@ -27,6 +27,7 @@
|
|||||||
:selected-item-group="selectedItemGroup"
|
:selected-item-group="selectedItemGroup"
|
||||||
:is-pos-shift-open="isPosShiftOpen"
|
:is-pos-shift-open="isPosShiftOpen"
|
||||||
:items="(items as [] as POSItem[])"
|
:items="(items as [] as POSItem[])"
|
||||||
|
:item-visibility="itemVisibility"
|
||||||
:sinv-doc="(sinvDoc as SalesInvoice)"
|
:sinv-doc="(sinvDoc as SalesInvoice)"
|
||||||
:disable-pay-button="disablePayButton"
|
:disable-pay-button="disablePayButton"
|
||||||
:open-payment-modal="openPaymentModal"
|
:open-payment-modal="openPaymentModal"
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
:selected-item-group="selectedItemGroup"
|
:selected-item-group="selectedItemGroup"
|
||||||
:is-pos-shift-open="isPosShiftOpen"
|
:is-pos-shift-open="isPosShiftOpen"
|
||||||
:items="(items as [] as POSItem[])"
|
:items="(items as [] as POSItem[])"
|
||||||
|
:item-visibility="itemVisibility"
|
||||||
:sinv-doc="(sinvDoc as SalesInvoice)"
|
:sinv-doc="(sinvDoc as SalesInvoice)"
|
||||||
:disable-pay-button="disablePayButton"
|
:disable-pay-button="disablePayButton"
|
||||||
:open-payment-modal="openPaymentModal"
|
:open-payment-modal="openPaymentModal"
|
||||||
@@ -170,7 +172,9 @@ import {
|
|||||||
removeFreeItems,
|
removeFreeItems,
|
||||||
getItemRateFromPriceList,
|
getItemRateFromPriceList,
|
||||||
getItemVisibility,
|
getItemVisibility,
|
||||||
|
isLoyaltyProgramExpiredAndMaxed,
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
|
import { ItemVisibility } from 'src/components/POS/types';
|
||||||
import {
|
import {
|
||||||
POSItem,
|
POSItem,
|
||||||
ItemQtyMap,
|
ItemQtyMap,
|
||||||
@@ -267,6 +271,7 @@ export default defineComponent({
|
|||||||
selectedItemForBatch: '' as string,
|
selectedItemForBatch: '' as string,
|
||||||
pendingBatchItem: null as { item: POSItem; quantity: number } | null,
|
pendingBatchItem: null as { item: POSItem; quantity: number } | null,
|
||||||
expandedBatchId: undefined as string | null | undefined,
|
expandedBatchId: undefined as string | null | undefined,
|
||||||
|
itemVisibilityValue: 'Inventory Items' as ItemVisibility,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -276,6 +281,9 @@ export default defineComponent({
|
|||||||
return !!fyo.singles.AccountingSettings?.enableDiscounting;
|
return !!fyo.singles.AccountingSettings?.enableDiscounting;
|
||||||
},
|
},
|
||||||
isPosShiftOpen: () => !!fyo.singles.POSSettings?.isShiftOpen,
|
isPosShiftOpen: () => !!fyo.singles.POSSettings?.isShiftOpen,
|
||||||
|
itemVisibility() {
|
||||||
|
return this.itemVisibilityValue;
|
||||||
|
},
|
||||||
disablePayButton(): boolean {
|
disablePayButton(): boolean {
|
||||||
if (!this.sinvDoc.items?.length || !this.sinvDoc.party) {
|
if (!this.sinvDoc.items?.length || !this.sinvDoc.party) {
|
||||||
return true;
|
return true;
|
||||||
@@ -300,6 +308,7 @@ export default defineComponent({
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
await this.setItems();
|
await this.setItems();
|
||||||
await this.loadPOSProfile();
|
await this.loadPOSProfile();
|
||||||
|
this.itemVisibilityValue = await getItemVisibility(this.fyo);
|
||||||
},
|
},
|
||||||
async activated() {
|
async activated() {
|
||||||
toggleSidebar(false);
|
toggleSidebar(false);
|
||||||
@@ -503,7 +512,21 @@ export default defineComponent({
|
|||||||
filters: { name: value },
|
filters: { name: value },
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loyaltyProgram = party[0]?.loyaltyProgram as string;
|
const loyaltyProgramName = party[0]?.loyaltyProgram as string;
|
||||||
|
|
||||||
|
if (loyaltyProgramName) {
|
||||||
|
const isExpiredAndMaxed = await isLoyaltyProgramExpiredAndMaxed(
|
||||||
|
this.fyo,
|
||||||
|
loyaltyProgramName
|
||||||
|
);
|
||||||
|
if (isExpiredAndMaxed) {
|
||||||
|
this.loyaltyProgram = loyaltyProgramName;
|
||||||
|
this.loyaltyPoints = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loyaltyProgram = loyaltyProgramName;
|
||||||
this.loyaltyPoints = party[0]?.loyaltyPoints as number;
|
this.loyaltyPoints = party[0]?.loyaltyPoints as number;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -652,7 +675,8 @@ export default defineComponent({
|
|||||||
this.fyo.singles.AccountingSettings?.enablePriceList &&
|
this.fyo.singles.AccountingSettings?.enablePriceList &&
|
||||||
this.loyaltyPoints &&
|
this.loyaltyPoints &&
|
||||||
this.sinvDoc.party &&
|
this.sinvDoc.party &&
|
||||||
this.sinvDoc.items?.length
|
this.sinvDoc.items?.length &&
|
||||||
|
this.loyaltyProgram
|
||||||
) {
|
) {
|
||||||
this.toggleModal('LoyaltyProgram', true);
|
this.toggleModal('LoyaltyProgram', true);
|
||||||
}
|
}
|
||||||
@@ -702,8 +726,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (itemVisibility === 'Inventory Items') {
|
if (itemVisibility === 'Inventory Items') {
|
||||||
filters.trackItem = true;
|
filters.trackItem = true;
|
||||||
} else {
|
} else if (itemVisibility === 'ERP Sync Items') {
|
||||||
|
filters.datafromErp = true;
|
||||||
|
} else if (itemVisibility === 'Non-Inventory Items') {
|
||||||
filters.trackItem = false;
|
filters.trackItem = false;
|
||||||
|
filters.datafromErp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedItemGroup) {
|
if (this.selectedItemGroup) {
|
||||||
|
|||||||
@@ -76,7 +76,9 @@
|
|||||||
<div
|
<div
|
||||||
class="relative group"
|
class="relative group"
|
||||||
:class="{
|
:class="{
|
||||||
hidden: !fyo.singles.AccountingSettings?.enableLoyaltyProgram,
|
hidden:
|
||||||
|
!fyo.singles.AccountingSettings?.enableLoyaltyProgram ||
|
||||||
|
!loyaltyProgram,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -150,6 +150,10 @@ export async function syncDocumentsFromERPNext(fyo: Fyo) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getDocTypeName(doc) === ModelNameEnum.Item) {
|
||||||
|
doc.datafromErp = true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ((doc.fbooksDocName as string) || (doc.name as string)) {
|
if ((doc.fbooksDocName as string) || (doc.name as string)) {
|
||||||
const isDocExists = await fyo.db.exists(
|
const isDocExists = await fyo.db.exists(
|
||||||
@@ -239,6 +243,10 @@ async function createNewDocument(
|
|||||||
token: string,
|
token: string,
|
||||||
deviceID: string
|
deviceID: string
|
||||||
) {
|
) {
|
||||||
|
if (getDocTypeName(doc) === ModelNameEnum.Item) {
|
||||||
|
doc.datafromErp = true;
|
||||||
|
}
|
||||||
|
|
||||||
const newDoc = fyo.doc.getNewDoc(getDocTypeName(doc), doc);
|
const newDoc = fyo.doc.getNewDoc(getDocTypeName(doc), doc);
|
||||||
await performPreSync(fyo, doc);
|
await performPreSync(fyo, doc);
|
||||||
await appendDocValues(newDoc as DocValueMap, doc);
|
await appendDocValues(newDoc as DocValueMap, doc);
|
||||||
@@ -447,8 +455,10 @@ async function updateExistingDocument(
|
|||||||
token: string,
|
token: string,
|
||||||
deviceID: string
|
deviceID: string
|
||||||
) {
|
) {
|
||||||
|
const docType = getDocTypeName(doc);
|
||||||
|
|
||||||
const existingDoc = await fyo.doc.getDoc(
|
const existingDoc = await fyo.doc.getDoc(
|
||||||
getDocTypeName(doc),
|
docType,
|
||||||
(doc.fbooksDocName as string) || (doc.name as string)
|
(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) {
|
if (docsByType[docType] && docsByType[docType].length > 0) {
|
||||||
for (const doc of docsByType[docType]) {
|
for (const doc of docsByType[docType]) {
|
||||||
try {
|
try {
|
||||||
|
if (docType === ModelNameEnum.Item) {
|
||||||
|
doc.datafromErp = true;
|
||||||
|
}
|
||||||
|
|
||||||
const isDocExists = await fyo.db.exists(
|
const isDocExists = await fyo.db.exists(
|
||||||
docType,
|
docType,
|
||||||
(doc.fbooksDocName as string) || (doc.name as string)
|
(doc.fbooksDocName as string) || (doc.name as string)
|
||||||
|
|||||||
Reference in New Issue
Block a user