diff --git a/backend/database/bespoke.ts b/backend/database/bespoke.ts index c5ab61b8a..f2c855dc6 100644 --- a/backend/database/bespoke.ts +++ b/backend/database/bespoke.ts @@ -402,10 +402,7 @@ export class BespokeQueries { const sinvNamesQuery = db.knex!(ModelNameEnum.SalesInvoice) .select('name') .where('isPOS', true) - .andWhereBetween('date', [ - DateTime.fromJSDate(fromDate).toSQLDate(), - DateTime.fromJSDate(toDate).toSQLDate(), - ]); + .andWhereBetween('date', [fromDate.toISOString(), toDate.toISOString()]); if (lastShiftClosingDate) { sinvNamesQuery.andWhere( diff --git a/backend/patches/createPaymentMethods.ts b/backend/patches/createPaymentMethods.ts new file mode 100644 index 000000000..e4c746d2f --- /dev/null +++ b/backend/patches/createPaymentMethods.ts @@ -0,0 +1,57 @@ +import { ModelNameEnum } from 'models/types'; +import { DatabaseManager } from '../database/manager'; +import { AccountTypeEnum } from 'models/baseModels/Account/types'; +import { getDefaultMetaFieldValueMap } from 'backend/helpers'; + +type AccountTypeMap = Record; + +async function execute(dm: DatabaseManager) { + const accounts = (await dm.db?.getAll(ModelNameEnum.Account, { + fields: ['name', 'accountType'], + filters: { + accountType: [ + 'in', + [ + AccountTypeEnum.Bank, + AccountTypeEnum.Cash, + AccountTypeEnum.Payable, + AccountTypeEnum.Receivable, + ], + ], + }, + })) as { name: string; accountType: AccountTypeEnum }[]; + + const accountsMap = accounts.reduce((acc, ac) => { + acc[ac.accountType] ??= []; + acc[ac.accountType]!.push(ac.name); + return acc; + }, {} as AccountTypeMap); + + const defaults = getDefaultMetaFieldValueMap(); + + const paymentMethods = [ + { + name: 'Cash', + type: 'Cash', + account: accountsMap[AccountTypeEnum.Cash]?.[0], + ...defaults, + }, + { + name: 'Bank', + type: 'Bank', + account: accountsMap[AccountTypeEnum.Bank]?.[0], + ...defaults, + }, + { + name: 'Transfer', + type: 'Bank', + account: accountsMap[AccountTypeEnum.Bank]?.[0], + ...defaults, + }, + ]; + + for (const paymentMethod of paymentMethods) { + await dm.db?.insert(ModelNameEnum.PaymentMethod, paymentMethod); + } +} +export default { execute }; diff --git a/backend/patches/index.ts b/backend/patches/index.ts index 78edbe842..e2d332274 100644 --- a/backend/patches/index.ts +++ b/backend/patches/index.ts @@ -7,6 +7,7 @@ import updateSchemas from './updateSchemas'; import setPaymentReferenceType from './setPaymentReferenceType'; import fixLedgerDateTime from './v0_21_0/fixLedgerDateTime'; import fixItemHSNField from './fixItemHSNField'; +import createPaymentMethods from './createPaymentMethods'; export default [ { name: 'testPatch', version: '0.5.0-beta.0', patch: testPatch }, @@ -42,4 +43,9 @@ export default [ patch: fixLedgerDateTime, }, { name: 'fixItemHSNField', version: '0.24.0', patch: fixItemHSNField }, + { + name: 'createPaymentMethods', + version: '0.25.1', + patch: createPaymentMethods, + }, ] as Patch[]; diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index 3f021ec3c..c70a685bb 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -9,7 +9,6 @@ import { FormulaMap, HiddenMap, ListViewSettings, - RequiredMap, ValidationMap, } from 'fyo/model/types'; import { NotFoundError, ValidationError } from 'fyo/utils/errors'; @@ -27,8 +26,9 @@ import { AccountTypeEnum } from '../Account/types'; import { Invoice } from '../Invoice/Invoice'; import { Party } from '../Party/Party'; import { PaymentFor } from '../PaymentFor/PaymentFor'; -import { PaymentMethod, PaymentType } from './types'; +import { PaymentType } from './types'; import { TaxSummary } from '../TaxSummary/TaxSummary'; +import { PaymentMethod } from '../PaymentMethod/PaymentMethod'; type AccountTypeMap = Record; @@ -38,10 +38,15 @@ export class Payment extends Transactional { amount?: Money; writeoff?: Money; paymentType?: PaymentType; + paymentMethod?: string; referenceType?: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice; for?: PaymentFor[]; _accountsMap?: AccountTypeMap; + async paymentMethodDoc() { + return (await this.loadAndGetLink('paymentMethod')) as PaymentMethod; + } + async change({ changed }: ChangeArg) { if (changed === 'for') { this.updateAmountOnReferenceUpdate(); @@ -110,6 +115,7 @@ export class Payment extends Transactional { this.validateAccounts(); this.validateTotalReferenceAmount(); await this.validateReferences(); + await this.validateReferencesAreSet(); } async validateFor() { @@ -223,6 +229,22 @@ export class Payment extends Transactional { ); } + async validateReferencesAreSet() { + const type = (await this.paymentMethodDoc()).type; + + if (type !== 'Bank') { + return; + } + + if (!this.clearanceDate) { + throw new ValidationError(t`Clearance Date not set.`); + } + + if (!this.referenceId) { + throw new ValidationError(t`Reference Id not set.`); + } + } + async getTaxSummary() { const taxes: Record< string, @@ -559,15 +581,13 @@ export class Payment extends Transactional { ); } - if (this.paymentMethod === 'Cash') { - return accountsMap[AccountTypeEnum.Cash]?.[0] ?? null; - } + const paymentMethodDoc = await this.paymentMethodDoc(); - if (this.paymentMethod !== 'Cash') { - return accountsMap[AccountTypeEnum.Bank]?.[0] ?? null; + if (paymentMethodDoc.type === 'Cash') { + return accountsMap[AccountTypeEnum.Cash]?.[0] ?? null; } - return null; + return accountsMap[AccountTypeEnum.Bank]?.[0] ?? null; }, dependsOn: ['paymentMethod', 'paymentType', 'party'], }, @@ -582,15 +602,17 @@ export class Payment extends Transactional { ); } - if (this.paymentMethod === 'Cash') { - return accountsMap[AccountTypeEnum.Cash]?.[0] ?? null; + const paymentMethodDoc = await this.paymentMethodDoc(); + + if (paymentMethodDoc.account) { + return paymentMethodDoc.get('account'); } - if (this.paymentMethod !== 'Cash') { - return accountsMap[AccountTypeEnum.Bank]?.[0] ?? null; + if (paymentMethodDoc.type === 'Cash') { + return accountsMap[AccountTypeEnum.Cash]?.[0] ?? null; } - return null; + return accountsMap[AccountTypeEnum.Bank]?.[0] ?? null; }, dependsOn: ['paymentMethod', 'paymentType', 'party'], }, @@ -673,14 +695,7 @@ export class Payment extends Transactional { }, }; - required: RequiredMap = { - referenceId: () => this.paymentMethod !== 'Cash', - clearanceDate: () => this.paymentMethod !== 'Cash', - }; - hidden: HiddenMap = { - referenceId: () => this.paymentMethod === 'Cash', - clearanceDate: () => this.paymentMethod === 'Cash', amountPaid: () => this.writeoff?.isZero() ?? true, attachment: () => !(this.attachment || !(this.isSubmitted || this.isCancelled)), @@ -712,7 +727,7 @@ export class Payment extends Transactional { return { accountType: 'Receivable', isGroup: false }; } - if (paymentMethod === 'Cash') { + if (paymentMethod.name === 'Cash') { return { accountType: 'Cash', isGroup: false }; } else { return { accountType: ['in', ['Bank', 'Cash']], isGroup: false }; @@ -726,7 +741,7 @@ export class Payment extends Transactional { return { accountType: 'Payable', isGroup: false }; } - if (paymentMethod === 'Cash') { + if (paymentMethod.name === 'Cash') { return { accountType: 'Cash', isGroup: false }; } else { return { accountType: ['in', ['Bank', 'Cash']], isGroup: false }; diff --git a/models/baseModels/PaymentMethod/PaymentMethod.ts b/models/baseModels/PaymentMethod/PaymentMethod.ts new file mode 100644 index 000000000..48531c5a5 --- /dev/null +++ b/models/baseModels/PaymentMethod/PaymentMethod.ts @@ -0,0 +1,16 @@ +import { Doc } from 'fyo/model/doc'; +import { Account } from '../Account/Account'; +import { ListViewSettings } from 'fyo/model/types'; +import { PaymentMethodType } from 'models/types'; + +export class PaymentMethod extends Doc { + name?: string; + account?: Account; + type?: PaymentMethodType; + + static getListViewSettings(): ListViewSettings { + return { + columns: ['name', 'type'], + }; + } +} diff --git a/models/index.ts b/models/index.ts index cb7c39906..2f0b64604 100644 --- a/models/index.ts +++ b/models/index.ts @@ -16,6 +16,7 @@ import { Lead } from './baseModels/Lead/Lead'; import { AppliedCouponCodes } from './baseModels/AppliedCouponCodes/AppliedCouponCodes'; import { CouponCode } from './baseModels/CouponCode/CouponCode'; import { Payment } from './baseModels/Payment/Payment'; +import { PaymentMethod } from './baseModels/PaymentMethod/PaymentMethod'; import { PaymentFor } from './baseModels/PaymentFor/PaymentFor'; import { PriceList } from './baseModels/PriceList/PriceList'; import { PriceListItem } from './baseModels/PriceList/PriceListItem'; @@ -69,6 +70,7 @@ export const models = { CollectionRulesItems, CouponCode, Payment, + PaymentMethod, PaymentFor, PrintSettings, PriceList, diff --git a/models/types.ts b/models/types.ts index dcd2397d0..7b221228b 100644 --- a/models/types.ts +++ b/models/types.ts @@ -26,6 +26,7 @@ export enum ModelNameEnum { AppliedCouponCodes = 'AppliedCouponCodes', Payment = 'Payment', + PaymentMethod = 'PaymentMethod', PaymentFor = 'PaymentFor', PriceList = 'PriceList', PricingRule = 'PricingRule', @@ -68,3 +69,5 @@ export enum ModelNameEnum { } export type ModelName = keyof typeof ModelNameEnum; + +export type PaymentMethodType= 'Cash' | 'Bank' \ No newline at end of file diff --git a/schemas/app/Payment.json b/schemas/app/Payment.json index 9f30e2d8c..81d3fce94 100644 --- a/schemas/app/Payment.json +++ b/schemas/app/Payment.json @@ -81,23 +81,11 @@ "fieldname": "paymentMethod", "label": "Payment Method", "placeholder": "Payment Method", - "fieldtype": "Select", - "options": [ - { - "value": "Cash", - "label": "Cash" - }, - { - "value": "Cheque", - "label": "Cheque" - }, - { - "value": "Transfer", - "label": "Transfer" - } - ], + "fieldtype": "Link", + "target": "PaymentMethod", "default": "Cash", "required": true, + "create": true, "section": "Details" }, { diff --git a/schemas/app/PaymentMethod.json b/schemas/app/PaymentMethod.json new file mode 100644 index 000000000..89c99ff1b --- /dev/null +++ b/schemas/app/PaymentMethod.json @@ -0,0 +1,35 @@ +{ + "name": "PaymentMethod", + "label": "Payment Method", + "naming": "manual", + "fields": [ + { + "fieldname": "name", + "label": "Name", + "fieldtype": "Data" + }, + { + "fieldname": "type", + "label": "Type", + "fieldtype": "Select", + "required": true, + "options": [ + { + "value": "Cash", + "label": "Cash" + }, + { + "value": "Bank", + "label": "Bank" + } + ] + }, + { + "fieldname": "account", + "label": "Account", + "fieldtype": "Link", + "target": "Account" + } + ], + "quickEditFields": ["name", "type", "account"] +} diff --git a/schemas/schemas.ts b/schemas/schemas.ts index fcb3b1ba0..39f28675b 100644 --- a/schemas/schemas.ts +++ b/schemas/schemas.ts @@ -22,6 +22,7 @@ import CollectionRulesItems from './app/CollectionRulesItems.json'; import CouponCode from './app/CouponCode.json'; import AppliedCouponCodes from './app/AppliedCouponCodes.json'; import Payment from './app/Payment.json'; +import PaymentMethod from './app/PaymentMethod.json'; import PaymentFor from './app/PaymentFor.json'; import PriceList from './app/PriceList.json'; import PriceListItem from './app/PriceListItem.json'; @@ -117,6 +118,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [ CollectionRulesItems as Schema, Payment as Schema, + PaymentMethod as Schema, PaymentFor as Schema, JournalEntry as Schema, diff --git a/src/components/POS/types.ts b/src/components/POS/types.ts index ea31a0f46..950680c6f 100644 --- a/src/components/POS/types.ts +++ b/src/components/POS/types.ts @@ -25,7 +25,8 @@ export type PosEmits = | 'addItem' | 'toggleView' | 'toggleModal' - | 'setCashAmount' + | 'setPaidAmount' + | 'setPaymentMethod' | 'setCouponsCount' | 'routeToSinvList' | 'applyPricingRule' diff --git a/src/pages/POS/ClassicPOS.vue b/src/pages/POS/ClassicPOS.vue index b5b184631..88e27c9f5 100644 --- a/src/pages/POS/ClassicPOS.vue +++ b/src/pages/POS/ClassicPOS.vue @@ -43,9 +43,11 @@ ({ paymentMethod: doc.name, amount: fyo.pesa(0) })); + + await this.posShiftDoc.set('openingAmounts', paymentMethods); }, async seedDefaults() { if (!!this.posShiftDoc?.isShiftOpen) { diff --git a/src/pages/POS/POS.vue b/src/pages/POS/POS.vue index 6ce191632..8987f3e9e 100644 --- a/src/pages/POS/POS.vue +++ b/src/pages/POS/POS.vue @@ -20,7 +20,6 @@ :default-customer="defaultCustomer" :is-pos-shift-open="isPosShiftOpen" :items="(items as [] as POSItem[])" - :cash-amount="(cashAmount as Money)" :sinv-doc="(sinvDoc as SalesInvoice)" :disable-pay-button="disablePayButton" :open-payment-modal="openPaymentModal" @@ -39,7 +38,8 @@ @clear-values="clearValues" @set-customer="setCustomer" @toggle-modal="toggleModal" - @set-cash-amount="setCashAmount" + @set-paid-amount="setPaidAmount" + @set-payment-method="setPaymentMethod" @set-coupons-count="setCouponsCount" @route-to-sinv-list="routeToSinvList" @set-loyalty-points="setLoyaltyPoints" @@ -61,7 +61,6 @@ :default-customer="defaultCustomer" :is-pos-shift-open="isPosShiftOpen" :items="(items as [] as POSItem[])" - :cash-amount="(cashAmount as Money)" :sinv-doc="(sinvDoc as SalesInvoice)" :disable-pay-button="disablePayButton" :open-payment-modal="openPaymentModal" @@ -81,7 +80,8 @@ @clear-values="clearValues" @set-customer="setCustomer" @toggle-modal="toggleModal" - @set-cash-amount="setCashAmount" + @set-paid-amount="setPaidAmount" + @set-payment-method="setPaymentMethod" @set-coupons-count="setCouponsCount" @route-to-sinv-list="routeToSinvList" @apply-pricing-rule="applyPricingRule" @@ -155,7 +155,8 @@ export default defineComponent({ sinvDoc: computed(() => this.sinvDoc), coupons: computed(() => this.coupons), itemQtyMap: computed(() => this.itemQtyMap), - cashAmount: computed(() => this.cashAmount), + paidAmount: computed(() => this.paidAmount), + paymentMethod: computed(() => this.paymentMethod), transferRefNo: computed(() => this.transferRefNo), itemDiscounts: computed(() => this.itemDiscounts), transferAmount: computed(() => this.transferAmount), @@ -188,7 +189,7 @@ export default defineComponent({ openAppliedCouponsModal: false, totalQuantity: 0, - cashAmount: fyo.pesa(0), + paidAmount: fyo.pesa(0), itemDiscounts: fyo.pesa(0), transferAmount: fyo.pesa(0), totalTaxedAmount: fyo.pesa(0), @@ -202,6 +203,7 @@ export default defineComponent({ appliedCoupons: [] as AppliedCouponCodes[], itemSearchTerm: '', + paymentMethod: undefined as string | undefined, transferRefNo: undefined as string | undefined, defaultCustomer: undefined as string | undefined, transferClearanceDate: undefined as Date | undefined, @@ -396,8 +398,11 @@ export default defineComponent({ toggleView() { this.tableView = !this.tableView; }, - setCashAmount(amount: Money) { - this.cashAmount = amount; + setPaidAmount(amount: Money) { + this.paidAmount = amount; + }, + setPaymentMethod(method: string) { + this.paymentMethod = method; }, setDefaultCustomer() { this.defaultCustomer = this.fyo.singles.Defaults?.posCustomer ?? ''; @@ -579,22 +584,26 @@ export default defineComponent({ }, async makePayment() { this.paymentDoc = this.sinvDoc.getPayment() as Payment; - const paymentMethod = this.cashAmount.isZero() ? 'Transfer' : 'Cash'; + const paymentMethod = this.paymentMethod; await this.paymentDoc.set('paymentMethod', paymentMethod); - if (paymentMethod === 'Transfer') { + const paymentMethodDoc = await this.paymentDoc.loadAndGetLink( + 'paymentMethod' + ); + + if (paymentMethodDoc?.type !== 'Cash') { await this.paymentDoc.setMultiple({ - amount: this.transferAmount as Money, + amount: this.paidAmount as Money, referenceId: this.transferRefNo, clearanceDate: this.transferClearanceDate, }); } - if (paymentMethod === 'Cash') { + if (paymentMethodDoc?.type === 'Cash') { await this.paymentDoc.setMultiple({ paymentAccount: this.defaultPOSCashAccount, - amount: this.cashAmount as Money, + amount: this.paidAmount as Money, }); } @@ -688,7 +697,7 @@ export default defineComponent({ this.setSinvDoc(); this.itemSerialNumbers = {}; - this.cashAmount = fyo.pesa(0); + this.paidAmount = fyo.pesa(0); this.transferAmount = fyo.pesa(0); await this.setItems(); diff --git a/src/pages/POS/PaymentModal.vue b/src/pages/POS/PaymentModal.vue index 155686483..561b65ed1 100644 --- a/src/pages/POS/PaymentModal.vue +++ b/src/pages/POS/PaymentModal.vue @@ -1,43 +1,24 @@