From c1bba6b93f2952f1ef4bcb353f5ee1c5c0596dc6 Mon Sep 17 00:00:00 2001 From: iamkhanraheel Date: Thu, 5 Feb 2026 15:48:33 +0530 Subject: [PATCH 01/30] fix: update currency & exchange rate, update base & transaction fields lables --- frontend/src/components/ExpensesTable.vue | 18 +++ .../src/composables/updateCurrencyLabels.js | 50 +++++++++ frontend/src/views/employee_advance/Form.vue | 46 +++----- frontend/src/views/expense_claim/Form.vue | 104 +++++++++++++----- frontend/src/views/expense_claim/List.vue | 2 +- 5 files changed, 164 insertions(+), 56 deletions(-) create mode 100644 frontend/src/composables/updateCurrencyLabels.js diff --git a/frontend/src/components/ExpensesTable.vue b/frontend/src/components/ExpensesTable.vue index c12746d5cd..588ed46fd9 100644 --- a/frontend/src/components/ExpensesTable.vue +++ b/frontend/src/components/ExpensesTable.vue @@ -140,6 +140,8 @@ import CustomIonModal from "@/components/CustomIonModal.vue" import { claimTypesByID } from "@/data/claims" import { formatCurrency } from "@/utils/formatters" +import { updateCurrencyLabels } from "@/composables/updateCurrencyLabels" + const props = defineProps({ expenseClaim: { type: Object, @@ -243,4 +245,20 @@ watch( } } ) + +watch( + () => expensesTableFields.data, + (fields) => { + if (!fields) return + + updateCurrencyLabels({ + formFields: fields, + doc: props.expenseClaim, + baseFields: ["base_amount", "base_sanctioned_amount"], + transactionFields: ["amount", "sanctioned_amount"], + }) + }, + { immediate: true } +) + diff --git a/frontend/src/composables/updateCurrencyLabels.js b/frontend/src/composables/updateCurrencyLabels.js new file mode 100644 index 0000000000..8e24567179 --- /dev/null +++ b/frontend/src/composables/updateCurrencyLabels.js @@ -0,0 +1,50 @@ +import { ref, watch } from "vue" +import { getCompanyCurrency } from "@/data/currencies" + +export function updateCurrencyLabels({ formFields, doc, baseFields = [], transactionFields = []}) { + if (!formFields || !doc) return + + const companyCurrency = ref("") + + // fetch company currency initially or when company changes + const fetchCompanyCurrency = async () => { + if (!doc.company) return + companyCurrency.value = await getCompanyCurrency(doc.company) + } + + const currencyFields = new Set([...baseFields, ...transactionFields]) + + const updateLabels = () => { + if (!companyCurrency.value) return + + formFields.forEach((field) => { + if (!field?.fieldname) return + if (!currencyFields.has(field.fieldname)) return + + if (!field._original_label && field.label) { + field._original_label = field.label.replace(/\([^\)]*\)/g, "").trim() + } + + if (baseFields.includes(field.fieldname)) { + field.label = `${field._original_label} (${companyCurrency.value})` + field.hidden = doc.currency === companyCurrency.value + } + + if (transactionFields.includes(field.fieldname)) { + field.label = `${field._original_label} (${doc.currency})` + } + }) + } + + // update labels script + watch( + () => [doc.company, doc.currency], + async () => { + await fetchCompanyCurrency() + updateLabels() + }, + { immediate: true } + ) + + return { updateLabels, companyCurrency } +} diff --git a/frontend/src/views/employee_advance/Form.vue b/frontend/src/views/employee_advance/Form.vue index 8697eff316..d52879fa44 100644 --- a/frontend/src/views/employee_advance/Form.vue +++ b/frontend/src/views/employee_advance/Form.vue @@ -23,6 +23,7 @@ import { ref, watch, inject, computed } from "vue" import FormView from "@/components/FormView.vue" import { getCompanyCurrency } from "@/data/currencies" +import { updateCurrencyLabels } from "../../composables/updateCurrencyLabels" const employee = inject("$employee") @@ -65,14 +66,6 @@ const employeeCurrency = createResource({ params: { employee: employee.data.name }, onSuccess(data) { employeeAdvance.value.currency = data - setExchangeRate() - }, -}) - -const exchangeRate = createResource({ - url: "erpnext.setup.utils.get_exchange_rate", - onSuccess(data) { - employeeAdvance.value.exchange_rate = data }, }) @@ -84,12 +77,25 @@ const advanceAccount = createResource({ }, }) -// form scripts +// scripts + watch( - () => employeeAdvance.value.currency, - () => setExchangeRate() + () => [formFields.data, employeeAdvance.value.currency], + ([fields, currency]) => { + if (!fields || !currency) return + + updateCurrencyLabels({ + formFields: fields, + doc: employeeAdvance.value, + baseFields: ["base_paid_amount"], + transactionFields: ["paid_amount"], + }) + }, + { immediate: true } ) + + // helper functions function getFilteredFields(fields) { // reduce noise from the form view by excluding unnecessary fields @@ -129,23 +135,5 @@ function applyFilters(fields) { }) } -function setExchangeRate() { - if (!employeeAdvance.value.currency) return - const exchange_rate_field = formFields.data?.find( - (field) => field.fieldname === "exchange_rate" - ) - - if (employeeAdvance.value.currency === companyCurrency.value) { - employeeAdvance.value.exchange_rate = 1 - exchange_rate_field.hidden = 1 - } else { - exchangeRate.fetch({ - from_currency: employeeAdvance.value.currency, - to_currency: companyCurrency.value, - }) - exchange_rate_field.hidden = 0 - } -} - function validateForm() {} \ No newline at end of file diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index 661e473d3b..f92c0c83aa 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -60,7 +60,7 @@ import ExpensesTable from "@/components/ExpensesTable.vue" import ExpenseTaxesTable from "@/components/ExpenseTaxesTable.vue" import ExpenseAdvancesTable from "@/components/ExpenseAdvancesTable.vue" -import { getCompanyCurrency } from "@/data/currencies" +import { updateCurrencyLabels } from "@/composables/updateCurrencyLabels" const dayjs = inject("$dayjs") @@ -90,9 +90,10 @@ const tabs = [ const expenseClaim = ref({ employee: currEmployee, company: employeeCompany, + doctype: "Expense Claim" }) -const currency = computed(() => getCompanyCurrency(expenseClaim.value.company)) +const currency = computed(() => expenseClaim.value.currency) // get form fields const formFields = createResource({ @@ -109,6 +110,7 @@ const formFields = createResource({ onSuccess(_data) { expenseApproverDetails.reload() companyDetails.reload() + employeeCurrency.reload() }, }) formFields.reload() @@ -116,35 +118,22 @@ formFields.reload() // resources const advances = createResource({ url: "hrms.hr.doctype.expense_claim.expense_claim.get_advances", - params: { employee: currEmployee.value }, + params: { expense_claim: expenseClaim.value }, auto: true, onSuccess(data) { // set advances - if (props.id) { - expenseClaim.value.advances?.map((advance) => (advance.selected = true)) - } else { + if (!data) { expenseClaim.value.advances = [] + return } - return data.forEach((advance) => { - if ( - props.id && - expenseClaim.value.advances?.some( - (entry) => entry.employee_advance === advance.name - ) - ) - return - - expenseClaim.value.advances?.push({ - employee_advance: advance.name, - purpose: advance.purpose, - posting_date: advance.posting_date, - advance_account: advance.advance_account, - advance_paid: advance.paid_amount, - unclaimed_amount: advance.paid_amount - advance.claimed_amount, - allocated_amount: 0, + if (!props.id) { + data.forEach((advance) => { + advance.allocated_amount = 0 + advance.selected = true }) - }) + expenseClaim.value.advances = data + } }, }) @@ -166,6 +155,21 @@ const companyDetails = createResource({ }, }) +const employeeCurrency = createResource({ + url: "hrms.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency", + params: { employee: currEmployee.value }, + onSuccess(data) { + expenseClaim.value.currency = data + }, +}) + +const exchangeRate = createResource({ + url: "erpnext.setup.utils.get_exchange_rate", + onSuccess(data) { + expenseClaim.value.exchange_rate = data + }, +}) + // form scripts watch( () => expenseClaim.value.employee, @@ -186,9 +190,13 @@ watch( } ) watch( - () => props.id && expenseClaim.value.expenses, + () => expenseClaim.value.currency, + () => setExchangeRate() +) +watch( + () => expenseClaim.value.expenses, (_) => { - if (expenseClaim.value.docstatus === 0) { + if (!props.id && expenseClaim.value.docstatus === 0) { advances.reload() } } @@ -211,6 +219,33 @@ watch( } ) +watch( + () => [formFields.data, expenseClaim.value.currency], + ([fields, currency]) => { + if (!fields || !currency) return + + updateCurrencyLabels({ + formFields: fields, + doc: expenseClaim.value, + baseFields: [ + "base_total_sanctioned_amount", + "base_total_taxes_and_charges", + "base_total_advance_amount", + "base_grand_total", + "base_total_claimed_amount" + ], + transactionFields: [ + "total_sanctioned_amount", + "total_taxes_and_charges", + "total_advance_amount", + "grand_total", + "total_claimed_amount" + ], + }) + }, + { immediate: true } +) + // helper functions function getFilteredFields(fields) { // reduce noise from the form view by excluding unnecessary fields @@ -413,4 +448,21 @@ function validateForm() { }) } +function setExchangeRate() { + if (!expenseClaim.value.currency) return + const exchange_rate_field = formFields.data?.find( + (field) => field.fieldname === "exchange_rate" + ) + + if (expenseClaim.value.currency === currency.value) { + expenseClaim.value.exchange_rate = 1 + exchange_rate_field.hidden = 1 + } else { + exchangeRate.fetch({ + from_currency: expenseClaim.value.currency, + to_currency: currency.value, + }) + exchange_rate_field.hidden = 0 + } +} \ No newline at end of file diff --git a/frontend/src/views/expense_claim/List.vue b/frontend/src/views/expense_claim/List.vue index 0edc3aa7e8..a39102cd78 100644 --- a/frontend/src/views/expense_claim/List.vue +++ b/frontend/src/views/expense_claim/List.vue @@ -28,7 +28,7 @@ const EXPENSE_CLAIM_FIELDS = [ "`tabExpense Claim`.posting_date", "`tabExpense Claim`.company", "`tabExpense Claim Detail`.expense_type", - "count(`tabExpense Claim Detail`.expense_type) as total_expenses", + {"COUNT": "`tabExpense Claim Detail`.expense_type", "as":"total_expenses"} ] const FILTER_CONFIG = [ From 370b4691e97ef82a057a5bb6a1f937db7f98b42c Mon Sep 17 00:00:00 2001 From: iamkhanraheel Date: Wed, 25 Feb 2026 14:17:09 +0530 Subject: [PATCH 02/30] fix: advance child table row selection as per the sanctioned amount in claim, fix currency value in list & form view --- frontend/src/components/ExpenseClaimItem.vue | 4 +- .../src/composables/updateCurrencyLabels.js | 9 +-- frontend/src/views/employee_advance/Form.vue | 12 --- frontend/src/views/expense_claim/Form.vue | 76 +++++++++++-------- frontend/src/views/expense_claim/List.vue | 1 + 5 files changed, 49 insertions(+), 53 deletions(-) diff --git a/frontend/src/components/ExpenseClaimItem.vue b/frontend/src/components/ExpenseClaimItem.vue index cceb4c7762..cf9e960100 100644 --- a/frontend/src/components/ExpenseClaimItem.vue +++ b/frontend/src/components/ExpenseClaimItem.vue @@ -14,7 +14,7 @@ {{ claimDates }} · - {{ formatCurrency(props.doc.total_claimed_amount, currency) }} + {{ formatCurrency(props.doc.total_claimed_amount, props.doc.currency) }} @@ -33,7 +33,6 @@ import { computed, inject } from "vue" import ListItem from "@/components/ListItem.vue" import ExpenseIcon from "@/components/icons/ExpenseIcon.vue" -import { getCompanyCurrency } from "@/data/currencies" import { formatCurrency } from "@/utils/formatters" const dayjs = inject("$dayjs") @@ -99,7 +98,6 @@ const claimDates = computed(() => { } }) -const currency = computed(() => getCompanyCurrency(props.doc.company)) const approvalStatus = computed(() => { return props.doc.approval_status === "Draft" ? "Pending" : props.doc.approval_status diff --git a/frontend/src/composables/updateCurrencyLabels.js b/frontend/src/composables/updateCurrencyLabels.js index 8e24567179..ed58d2d4eb 100644 --- a/frontend/src/composables/updateCurrencyLabels.js +++ b/frontend/src/composables/updateCurrencyLabels.js @@ -1,11 +1,9 @@ import { ref, watch } from "vue" import { getCompanyCurrency } from "@/data/currencies" -export function updateCurrencyLabels({ formFields, doc, baseFields = [], transactionFields = []}) { +export function updateCurrencyLabels({ formFields, doc, baseFields = [], transactionFields = []}) { if (!formFields || !doc) return - const companyCurrency = ref("") - // fetch company currency initially or when company changes const fetchCompanyCurrency = async () => { if (!doc.company) return @@ -13,7 +11,6 @@ export function updateCurrencyLabels({ formFields, doc, baseFields = [], transac } const currencyFields = new Set([...baseFields, ...transactionFields]) - const updateLabels = () => { if (!companyCurrency.value) return @@ -24,19 +21,17 @@ export function updateCurrencyLabels({ formFields, doc, baseFields = [], transac if (!field._original_label && field.label) { field._original_label = field.label.replace(/\([^\)]*\)/g, "").trim() } - if (baseFields.includes(field.fieldname)) { field.label = `${field._original_label} (${companyCurrency.value})` field.hidden = doc.currency === companyCurrency.value } - if (transactionFields.includes(field.fieldname)) { field.label = `${field._original_label} (${doc.currency})` } }) } - // update labels script + // update labels watch( () => [doc.company, doc.currency], async () => { diff --git a/frontend/src/views/employee_advance/Form.vue b/frontend/src/views/employee_advance/Form.vue index d52879fa44..8985a7ab40 100644 --- a/frontend/src/views/employee_advance/Form.vue +++ b/frontend/src/views/employee_advance/Form.vue @@ -55,20 +55,11 @@ const formFields = createResource({ return applyFilters(fields) }, onSuccess(_) { - employeeCurrency.reload() advanceAccount.reload() }, }) formFields.reload() -const employeeCurrency = createResource({ - url: "hrms.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency", - params: { employee: employee.data.name }, - onSuccess(data) { - employeeAdvance.value.currency = data - }, -}) - const advanceAccount = createResource({ url: "hrms.api.get_advance_account", params: { company: employeeAdvance.value.company }, @@ -78,7 +69,6 @@ const advanceAccount = createResource({ }) // scripts - watch( () => [formFields.data, employeeAdvance.value.currency], ([fields, currency]) => { @@ -94,8 +84,6 @@ watch( { immediate: true } ) - - // helper functions function getFilteredFields(fields) { // reduce noise from the form view by excluding unnecessary fields diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index f92c0c83aa..ece572768d 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -59,7 +59,7 @@ import FormView from "@/components/FormView.vue" import ExpensesTable from "@/components/ExpensesTable.vue" import ExpenseTaxesTable from "@/components/ExpenseTaxesTable.vue" import ExpenseAdvancesTable from "@/components/ExpenseAdvancesTable.vue" - +import { getCompanyCurrency } from "@/data/currencies" import { updateCurrencyLabels } from "@/composables/updateCurrencyLabels" @@ -90,10 +90,11 @@ const tabs = [ const expenseClaim = ref({ employee: currEmployee, company: employeeCompany, - doctype: "Expense Claim" + doctype: "Expense Claim", }) const currency = computed(() => expenseClaim.value.currency) +const companyCurrency = computed(() => getCompanyCurrency(expenseClaim.value.company)) // get form fields const formFields = createResource({ @@ -110,7 +111,6 @@ const formFields = createResource({ onSuccess(_data) { expenseApproverDetails.reload() companyDetails.reload() - employeeCurrency.reload() }, }) formFields.reload() @@ -120,19 +120,19 @@ const advances = createResource({ url: "hrms.hr.doctype.expense_claim.expense_claim.get_advances", params: { expense_claim: expenseClaim.value }, auto: true, + transform(data) { + if (!data) return [] + return data.map((item) => ({ + ...item, + selected: parseFloat(item.allocated_amount || 0) > 0, + allocated_amount: item.allocated_amount || 0 + })) + }, onSuccess(data) { - // set advances - if (!data) { - expenseClaim.value.advances = [] - return - } - - if (!props.id) { - data.forEach((advance) => { - advance.allocated_amount = 0 - advance.selected = true - }) + // Only replace if the resource found data + if (data && data.length > 0) { expenseClaim.value.advances = data + calculateTotalAdvance() } }, }) @@ -155,14 +155,6 @@ const companyDetails = createResource({ }, }) -const employeeCurrency = createResource({ - url: "hrms.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency", - params: { employee: currEmployee.value }, - onSuccess(data) { - expenseClaim.value.currency = data - }, -}) - const exchangeRate = createResource({ url: "erpnext.setup.utils.get_exchange_rate", onSuccess(data) { @@ -191,7 +183,7 @@ watch( ) watch( () => expenseClaim.value.currency, - () => setExchangeRate() + () => setExchangeRate(), ) watch( () => expenseClaim.value.expenses, @@ -210,6 +202,26 @@ watch( { deep: true } ) +watch( + () => expenseClaim.value, + (newDoc) => { + if (newDoc?.advances?.length > 0) { + let needsRecalc = false + newDoc.advances.forEach(advance => { + // Reapply the "selected" flag if money is allocated + if (parseFloat(advance.allocated_amount || 0) > 0 && !advance.selected) { + advance.selected = true + needsRecalc = true + } + }) + if (needsRecalc) { + calculateTotalAdvance() + } + } + }, + { immediate: true } +) + watch( () => expenseClaim.value.cost_center, () => { @@ -399,6 +411,8 @@ function allocateAdvanceAmount() { let amount_to_be_allocated = parseFloat(expenseClaim.value.total_sanctioned_amount) + parseFloat(expenseClaim.value.total_taxes_and_charges) + + if (!amount_to_be_allocated) return let total_advance_amount = 0 expenseClaim?.value?.advances?.forEach((advance) => { @@ -422,8 +436,8 @@ function calculateTotalAdvance() { let total_advance_amount = 0 expenseClaim?.value?.advances?.forEach((advance) => { - if (advance.selected) { - total_advance_amount += parseFloat(advance.allocated_amount) + if (advance.selected || parseFloat(advance.allocated_amount) > 0) { + total_advance_amount += parseFloat(advance.allocated_amount || 0) } }) expenseClaim.value.total_advance_amount = total_advance_amount @@ -449,20 +463,20 @@ function validateForm() { } function setExchangeRate() { - if (!expenseClaim.value.currency) return + if (!expenseClaim.value.currency || !formFields.data) return const exchange_rate_field = formFields.data?.find( (field) => field.fieldname === "exchange_rate" ) - if (expenseClaim.value.currency === currency.value) { + if (currency.value === companyCurrency.value) { expenseClaim.value.exchange_rate = 1 - exchange_rate_field.hidden = 1 + if (exchange_rate_field) exchange_rate_field.hidden = 1 } else { exchangeRate.fetch({ - from_currency: expenseClaim.value.currency, - to_currency: currency.value, + from_currency: currency.value, + to_currency: companyCurrency.value, }) - exchange_rate_field.hidden = 0 + if (exchange_rate_field) exchange_rate_field.hidden = 0 } } \ No newline at end of file diff --git a/frontend/src/views/expense_claim/List.vue b/frontend/src/views/expense_claim/List.vue index a39102cd78..07e656a92e 100644 --- a/frontend/src/views/expense_claim/List.vue +++ b/frontend/src/views/expense_claim/List.vue @@ -21,6 +21,7 @@ const EXPENSE_CLAIM_FIELDS = [ "`tabExpense Claim`.name", "`tabExpense Claim`.employee", "`tabExpense Claim`.employee_name", + "`tabExpense Claim`.currency", "`tabExpense Claim`.approval_status", "`tabExpense Claim`.status", "`tabExpense Claim`.expense_approver", From 2b0dc143f3e92c12ec1af195ca7549e4006e431f Mon Sep 17 00:00:00 2001 From: iamkhanraheel Date: Wed, 25 Feb 2026 17:35:25 +0530 Subject: [PATCH 03/30] fix: update base fields value as per the exchange rate in real time --- frontend/src/components/ExpensesTable.vue | 30 ++++++++++++++++++- ...encyLabels.js => useCurrencyConversion.js} | 18 ++++++++++- frontend/src/data/currencies.js | 10 +++++++ frontend/src/views/employee_advance/Form.vue | 2 +- frontend/src/views/expense_claim/Form.vue | 28 ++++++++++++++++- 5 files changed, 84 insertions(+), 4 deletions(-) rename frontend/src/composables/{updateCurrencyLabels.js => useCurrencyConversion.js} (68%) diff --git a/frontend/src/components/ExpensesTable.vue b/frontend/src/components/ExpensesTable.vue index 588ed46fd9..1d8adfcef7 100644 --- a/frontend/src/components/ExpensesTable.vue +++ b/frontend/src/components/ExpensesTable.vue @@ -140,7 +140,7 @@ import CustomIonModal from "@/components/CustomIonModal.vue" import { claimTypesByID } from "@/data/claims" import { formatCurrency } from "@/utils/formatters" -import { updateCurrencyLabels } from "@/composables/updateCurrencyLabels" +import { updateCurrencyLabels, updateBaseFieldsAmount } from "@/composables/useCurrencyConversion" const props = defineProps({ expenseClaim: { @@ -261,4 +261,32 @@ watch( { immediate: true } ) +watch( + () => [expenseItem.value.amount, expenseItem.value.sanctioned_amount], + () => { + if (expenseItem.value) { + updateBaseFieldsAmount({ + doc: expenseItem.value, + fields: ['amount', 'sanctioned_amount'], + exchangeRate: props.expenseClaim.exchange_rate, + }); + } + } +); + +watch( + () => props.expenseClaim.exchange_rate, + (exchangeRate) => { + if (props.expenseClaim.expenses) { + props.expenseClaim.expenses.forEach(row => { + updateBaseFieldsAmount({ + doc:row, + fields:['amount', 'sanctioned_amount'], + exchangeRate: exchangeRate + }); + }); + } + } +); + diff --git a/frontend/src/composables/updateCurrencyLabels.js b/frontend/src/composables/useCurrencyConversion.js similarity index 68% rename from frontend/src/composables/updateCurrencyLabels.js rename to frontend/src/composables/useCurrencyConversion.js index ed58d2d4eb..1801e4333e 100644 --- a/frontend/src/composables/updateCurrencyLabels.js +++ b/frontend/src/composables/useCurrencyConversion.js @@ -1,5 +1,11 @@ import { ref, watch } from "vue" -import { getCompanyCurrency } from "@/data/currencies" +import { getCompanyCurrency, currencyPrecision } from "@/data/currencies" + +const flt = (value, precision) => { + const num = parseFloat(value) || 0; + const targetPrecision = precision !== undefined ? precision : (currencyPrecision.data || 2); + return parseFloat(num.toFixed(targetPrecision)); +}; export function updateCurrencyLabels({ formFields, doc, baseFields = [], transactionFields = []}) { if (!formFields || !doc) return @@ -43,3 +49,13 @@ export function updateCurrencyLabels({ formFields, doc, baseFields = [], transac return { updateLabels, companyCurrency } } + +// function to update base currency fields data +export function updateBaseFieldsAmount({doc, fields, exchangeRate}) { + if (!doc) return; + const excahnge_rate = flt(exchangeRate || doc.exchange_rate || 1, 9); + fields.forEach(f => { + const val = flt(flt(doc[f]) * excahnge_rate); + doc["base_" + f] = val; + }); +} \ No newline at end of file diff --git a/frontend/src/data/currencies.js b/frontend/src/data/currencies.js index e95d3f99b1..25533df808 100644 --- a/frontend/src/data/currencies.js +++ b/frontend/src/data/currencies.js @@ -21,3 +21,13 @@ export function getCompanyCurrencySymbol(company) { export function getCurrencySymbol(currency) { return currencySymbols?.data?.[currency] } + +export const currencyPrecision = createResource({ + url: "frappe.client.get_single_value", + params: { + doctype: "System Settings", + field: "currency_precision" + }, + auto: true, + initialData: 2 +}); \ No newline at end of file diff --git a/frontend/src/views/employee_advance/Form.vue b/frontend/src/views/employee_advance/Form.vue index 8985a7ab40..1144594026 100644 --- a/frontend/src/views/employee_advance/Form.vue +++ b/frontend/src/views/employee_advance/Form.vue @@ -23,7 +23,7 @@ import { ref, watch, inject, computed } from "vue" import FormView from "@/components/FormView.vue" import { getCompanyCurrency } from "@/data/currencies" -import { updateCurrencyLabels } from "../../composables/updateCurrencyLabels" +import { updateCurrencyLabels } from "@/composables/useCurrencyConversion" const employee = inject("$employee") diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index ece572768d..acbe97a4cf 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -60,7 +60,7 @@ import ExpensesTable from "@/components/ExpensesTable.vue" import ExpenseTaxesTable from "@/components/ExpenseTaxesTable.vue" import ExpenseAdvancesTable from "@/components/ExpenseAdvancesTable.vue" import { getCompanyCurrency } from "@/data/currencies" -import { updateCurrencyLabels } from "@/composables/updateCurrencyLabels" +import { updateCurrencyLabels, updateBaseFieldsAmount } from "@/composables/useCurrencyConversion" const dayjs = inject("$dayjs") @@ -258,6 +258,32 @@ watch( { immediate: true } ) +watch( + () => [ + expenseClaim.value.total_sanctioned_amount, + expenseClaim.value.total_advance_amount, + expenseClaim.value.grand_total, + expenseClaim.value.total_claimed_amount, + expenseClaim.value.total_taxes_and_charges, + expenseClaim.value.exchange_rate + ], + () => { + const fieldsToConvert = [ + "total_sanctioned_amount", + "total_advance_amount", + "grand_total", + "total_claimed_amount", + "total_taxes_and_charges" + ]; + updateBaseFieldsAmount({ + doc: expenseClaim.value, + fields: fieldsToConvert, + exchangeRate: expenseClaim.value.exchange_rate, + }); + }, + { deep: true } +); + // helper functions function getFilteredFields(fields) { // reduce noise from the form view by excluding unnecessary fields From a767d69c052a4e9ebc5b167820e3daee35787b31 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 5 Mar 2026 17:49:13 +0530 Subject: [PATCH 04/30] fix(Link): reload link field options when filters change --- frontend/src/components/Link.vue | 22 +++++++++++++++------- roster/src/components/Link.vue | 5 +++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Link.vue b/frontend/src/components/Link.vue index 5ccea6c5b3..577b3faed4 100644 --- a/frontend/src/components/Link.vue +++ b/frontend/src/components/Link.vue @@ -43,7 +43,10 @@ const searchText = ref("") const value = computed({ get: () => props.modelValue, set: (val) => { - const newVal = (val && typeof val === "object" && val.value !== undefined) ? val.value : val + const newVal = + val && typeof val === "object" && val.value !== undefined + ? val.value + : val emit("update:modelValue", newVal || "") }, }) @@ -72,20 +75,20 @@ const reloadOptions = (searchTextVal) => { params: { txt: searchTextVal, doctype: props.doctype, - filters: props.filters + filters: props.filters, }, }) options.reload() } const handleQueryUpdate = debounce((newQuery) => { - const val = newQuery || "" + const val = newQuery || "" - if (val === "" && props.modelValue) return + if (val === "" && props.modelValue) return - if (searchText.value === val) return - searchText.value = val - reloadOptions(val) + if (searchText.value === val) return + searchText.value = val + reloadOptions(val) }, 300) watch( @@ -96,4 +99,9 @@ watch( }, { immediate: true } ) + +watch( + () => props.filters, + () => reloadOptions(''), +) diff --git a/roster/src/components/Link.vue b/roster/src/components/Link.vue index 3f6056dfe6..feb5a666e2 100644 --- a/roster/src/components/Link.vue +++ b/roster/src/components/Link.vue @@ -104,4 +104,9 @@ watch( }, { immediate: true }, ); + +watch( + () => props.filters, + () => reloadOptions(""), +); From 0912671ac2068511a164cf9f37275d356cc3bece Mon Sep 17 00:00:00 2001 From: iamkhanraheel Date: Fri, 6 Mar 2026 12:43:54 +0530 Subject: [PATCH 05/30] fix: update currency in advance account filters, remove fetching default account api --- frontend/src/views/employee_advance/Form.vue | 39 +++++++------------- hrms/api/__init__.py | 5 --- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/frontend/src/views/employee_advance/Form.vue b/frontend/src/views/employee_advance/Form.vue index 1144594026..c0cedac7d9 100644 --- a/frontend/src/views/employee_advance/Form.vue +++ b/frontend/src/views/employee_advance/Form.vue @@ -18,11 +18,9 @@ \ No newline at end of file From 1164ff88bbbda9bd29dd20fee48e56e60bd062a4 Mon Sep 17 00:00:00 2001 From: iamkhanraheel Date: Thu, 26 Mar 2026 17:36:49 +0530 Subject: [PATCH 08/30] fix: formatting --- frontend/src/components/ExpensesTable.vue | 8 ++--- frontend/src/views/expense_claim/Form.vue | 38 +++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/ExpensesTable.vue b/frontend/src/components/ExpensesTable.vue index 1d8adfcef7..c1f551176d 100644 --- a/frontend/src/components/ExpensesTable.vue +++ b/frontend/src/components/ExpensesTable.vue @@ -252,10 +252,10 @@ watch( if (!fields) return updateCurrencyLabels({ - formFields: fields, - doc: props.expenseClaim, - baseFields: ["base_amount", "base_sanctioned_amount"], - transactionFields: ["amount", "sanctioned_amount"], + formFields: fields, + doc: props.expenseClaim, + baseFields: ["base_amount", "base_sanctioned_amount"], + transactionFields: ["amount", "sanctioned_amount"], }) }, { immediate: true } diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index 7ddd8db1fb..ee5015742d 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -267,29 +267,29 @@ watch( ) watch( - () => [ - expenseClaim.value.total_sanctioned_amount, - expenseClaim.value.total_advance_amount, - expenseClaim.value.grand_total, - expenseClaim.value.total_claimed_amount, - expenseClaim.value.total_taxes_and_charges, - expenseClaim.value.exchange_rate - ], - () => { - const fieldsToConvert = [ - "total_sanctioned_amount", - "total_advance_amount", - "grand_total", - "total_claimed_amount", - "total_taxes_and_charges" - ]; - updateBaseFieldsAmount({ + () => [ + expenseClaim.value.total_sanctioned_amount, + expenseClaim.value.total_advance_amount, + expenseClaim.value.grand_total, + expenseClaim.value.total_claimed_amount, + expenseClaim.value.total_taxes_and_charges, + expenseClaim.value.exchange_rate + ], + () => { + const fieldsToConvert = [ + "total_sanctioned_amount", + "total_advance_amount", + "grand_total", + "total_claimed_amount", + "total_taxes_and_charges" + ]; + updateBaseFieldsAmount({ doc: expenseClaim.value, fields: fieldsToConvert, exchangeRate: expenseClaim.value.exchange_rate, }); - }, - { deep: true } + }, + { deep: true } ); // helper functions From 604dc0706c1c6d03214b396a4fa5bb4fce4e7d06 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Mar 2026 11:59:49 +0530 Subject: [PATCH 09/30] fix: currency not showing up in recent expenses --- hrms/api/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index ddfe61eee5..5414b6f9ac 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -495,6 +495,7 @@ def get_expense_claims( "`tabExpense Claim`.posting_date", "`tabExpense Claim`.employee", "`tabExpense Claim`.employee_name", + "`tabExpense Claim`.currency", "`tabExpense Claim`.approval_status", "`tabExpense Claim`.status", "`tabExpense Claim`.expense_approver", From 6972cea848bcc8104bac1b2ac4f028091c55faf3 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Mar 2026 12:17:42 +0530 Subject: [PATCH 10/30] fix(Link): new options don't populate when search term changes --- frontend/src/components/Link.vue | 3 --- roster/src/components/Link.vue | 3 --- 2 files changed, 6 deletions(-) diff --git a/frontend/src/components/Link.vue b/frontend/src/components/Link.vue index 577b3faed4..5b7d74c1e1 100644 --- a/frontend/src/components/Link.vue +++ b/frontend/src/components/Link.vue @@ -83,9 +83,6 @@ const reloadOptions = (searchTextVal) => { const handleQueryUpdate = debounce((newQuery) => { const val = newQuery || "" - - if (val === "" && props.modelValue) return - if (searchText.value === val) return searchText.value = val reloadOptions(val) diff --git a/roster/src/components/Link.vue b/roster/src/components/Link.vue index feb5a666e2..80c5eebadd 100644 --- a/roster/src/components/Link.vue +++ b/roster/src/components/Link.vue @@ -88,9 +88,6 @@ const reloadOptions = (searchTextVal) => { const handleQueryUpdate = debounce((newQuery) => { const val = newQuery || ""; - - if (val === "" && props.modelValue) return; - if (searchText.value === val) return; searchText.value = val; reloadOptions(val); From 39083678e10f73f4a92b75fcd717d4d9a2f648ad Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Mar 2026 12:53:05 +0530 Subject: [PATCH 11/30] fix: fetching exchange rate fails when expense currency changes from company currency - also simplify value emit in Link field --- frontend/src/components/Link.vue | 10 +++++----- frontend/src/views/expense_claim/Form.vue | 19 ++++++------------- roster/src/components/Link.vue | 8 +++++--- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/Link.vue b/frontend/src/components/Link.vue index 5b7d74c1e1..016cf6c7c1 100644 --- a/frontend/src/components/Link.vue +++ b/frontend/src/components/Link.vue @@ -43,11 +43,11 @@ const searchText = ref("") const value = computed({ get: () => props.modelValue, set: (val) => { - const newVal = - val && typeof val === "object" && val.value !== undefined - ? val.value - : val - emit("update:modelValue", newVal || "") + if (typeof val === "string") { + emit("update:modelValue", val) + } else { + emit("update:modelValue", val?.value || "") + } }, }) diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index ee5015742d..98626ff458 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -43,7 +43,7 @@ :currency="currency" :isReadOnly="isReadOnly || isFormReadOnly" /> - + @@ -93,7 +93,6 @@ const expenseClaim = ref({ doctype: "Expense Claim", }) -const currency = computed(() => expenseClaim.value.currency) const companyCurrency = computed(() => getCompanyCurrency(expenseClaim.value.company)) // get form fields @@ -326,7 +325,7 @@ function applyFilters(field) { account_type: "Payable", company: expenseClaim.value.company, is_group: 0, - account_currency: currency.value, + account_currency: expenseClaim.value.currency, } } else if (field.fieldname === "cost_center") { field.linkFilters = { @@ -503,16 +502,10 @@ function setExchangeRate() { (field) => field.fieldname === "exchange_rate" ) - if (currency.value === companyCurrency.value) { - expenseClaim.value.exchange_rate = 1 - if (exchange_rate_field) exchange_rate_field.hidden = 1 - } - if (!expenseClaim.value.exchange_rate) { - exchangeRate.fetch({ - from_currency: currency.value, - to_currency: companyCurrency.value, - }) - } + exchangeRate.fetch({ + from_currency: expenseClaim.value.currency, + to_currency: companyCurrency.value, + }) if (exchange_rate_field) exchange_rate_field.hidden = 0 } \ No newline at end of file diff --git a/roster/src/components/Link.vue b/roster/src/components/Link.vue index 80c5eebadd..d473338dcc 100644 --- a/roster/src/components/Link.vue +++ b/roster/src/components/Link.vue @@ -51,9 +51,11 @@ const searchText = ref(""); const value = computed({ get: () => props.modelValue, set: (val) => { - const newVal = val && typeof val === "object" && val.value !== undefined ? val.value : val; - console.log(newVal); - emit("update:modelValue", newVal || ""); + if (typeof val === "string") { + emit("update:modelValue", val); + } else { + emit("update:modelValue", val?.value || ""); + } }, }); From 0f9ef2bfc4217dbd0508704720145eb8e3a50d18 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Mar 2026 15:03:47 +0530 Subject: [PATCH 12/30] fix: don't pass currency in expense claim tables, use doc currency --- frontend/src/components/ExpenseTaxesTable.vue | 12 ++++-------- frontend/src/components/ExpensesTable.vue | 18 +++++++----------- frontend/src/views/employee_advance/Form.vue | 2 +- frontend/src/views/expense_claim/Form.vue | 2 -- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/ExpenseTaxesTable.vue b/frontend/src/components/ExpenseTaxesTable.vue index 1bef2bc2a8..1afb5253e0 100644 --- a/frontend/src/components/ExpenseTaxesTable.vue +++ b/frontend/src/components/ExpenseTaxesTable.vue @@ -4,7 +4,7 @@

{{ __("Taxes & Charges") }}

- {{ formatCurrency(expenseClaim.total_taxes_and_charges, currency) }} + {{ formatCurrency(expenseClaim.total_taxes_and_charges, expenseClaim.currency) }}
- {{ formatCurrency(item.total, currency) }} + {{ formatCurrency(item.total, expenseClaim.currency) }}
@@ -140,10 +140,6 @@ const props = defineProps({ type: Object, required: true, }, - currency: { - type: String, - required: true, - }, isReadOnly: { type: Boolean, default: false, diff --git a/frontend/src/components/ExpensesTable.vue b/frontend/src/components/ExpensesTable.vue index c1f551176d..57fe856b21 100644 --- a/frontend/src/components/ExpensesTable.vue +++ b/frontend/src/components/ExpensesTable.vue @@ -4,7 +4,7 @@

{{ __("Expenses") }}

- {{ formatCurrency(expenseClaim.total_claimed_amount, currency) }} + {{ formatCurrency(expenseClaim.total_claimed_amount, expenseClaim.currency) }}
- {{ formatCurrency(item.amount, currency) }} + {{ formatCurrency(item.amount, expenseClaim.currency) }}
@@ -147,10 +147,6 @@ const props = defineProps({ type: Object, required: true, }, - currency: { - type: String, - required: true, - }, isReadOnly: { type: Boolean, default: false, @@ -266,8 +262,8 @@ watch( () => { if (expenseItem.value) { updateBaseFieldsAmount({ - doc: expenseItem.value, - fields: ['amount', 'sanctioned_amount'], + doc: expenseItem.value, + fields: ['amount', 'sanctioned_amount'], exchangeRate: props.expenseClaim.exchange_rate, }); } @@ -280,8 +276,8 @@ watch( if (props.expenseClaim.expenses) { props.expenseClaim.expenses.forEach(row => { updateBaseFieldsAmount({ - doc:row, - fields:['amount', 'sanctioned_amount'], + doc: row, + fields: ['amount', 'sanctioned_amount'], exchangeRate: exchangeRate }); }); diff --git a/frontend/src/views/employee_advance/Form.vue b/frontend/src/views/employee_advance/Form.vue index c0cedac7d9..e72c59c7a0 100644 --- a/frontend/src/views/employee_advance/Form.vue +++ b/frontend/src/views/employee_advance/Form.vue @@ -98,7 +98,7 @@ function applyFilters(fields) { return fields.map((field) => { if (field.fieldname === "advance_account") { if (!employeeAdvance.value.currency) return field - + field.linkFilters = { root_type: "Asset", is_group: 0, diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index 98626ff458..8da5605db7 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -18,7 +18,6 @@