diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml new file mode 100644 index 0000000000..b6659cc362 --- /dev/null +++ b/.github/workflows/patch.yml @@ -0,0 +1,173 @@ +name: Patch + +on: + pull_request: + paths-ignore: + - '**.js' + - '**.css' + - '**.md' + - '**.html' + - '**.csv' + - 'crowdin.yml' + - '.mergify.yml' + workflow_dispatch: + +concurrency: + group: patch-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 60 + + name: Patch Test + + services: + mysql: + image: mariadb:11.8 + env: + MARIADB_ROOT_PASSWORD: 'root' + ports: + - 3306:3306 + options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v6 + + - name: Check for valid Python & Merge Conflicts + run: | + python -m compileall -f "${GITHUB_WORKSPACE}" + if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}" + then echo "Found merge conflicts" + exit 1 + fi + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: | + 3.11 + 3.13 + 3.14 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: 24 + check-latest: true + + - name: Add to Hosts + run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Cache node modules + uses: actions/cache@v4 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v4 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install + run: | + pip install frappe-bench + bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + DB: mariadb + TYPE: server + + - name: Run Patch Tests + run: | + cd ~/frappe-bench/ + bench remove-app payments --force + bench remove-app lending --force + jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json + mv tmp.json ~/frappe-bench/sites/test_site/site_config.json + + wget https://frappe.io/files/frappe-hr-v14-p.sql.gz + bench --site test_site --force restore ~/frappe-bench/frappe-hr-v14-p.sql.gz + + git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git + git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git + git -C "apps/hrms" remote set-url upstream https://github.com/frappe/hrms.git + + + function update_to_version() { + version=$1 + + branch_name="version-$version-hotfix" + echo "Updating to v$version" + + # Fetch and checkout branches + git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name + git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name + git -C "apps/hrms" fetch --depth 1 upstream $branch_name:$branch_name + git -C "apps/frappe" checkout -q -f $branch_name + git -C "apps/erpnext" checkout -q -f $branch_name + git -C "apps/hrms" checkout -q -f $branch_name + + # Resetup env and install apps + pgrep honcho | xargs kill + rm -rf ~/frappe-bench/env + bench -v setup env --python python$2 + bench pip install -e ./apps/erpnext + bench pip install -e ./apps/hrms + bench start &>> ~/frappe-bench/bench_start.log & + + bench --site test_site migrate + } + + update_to_version 15 3.13 + update_to_version 16 3.14 + + echo "Updating to latest version" + git -C "apps/frappe" fetch --depth 1 upstream "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" + git -C "apps/frappe" checkout -q -f FETCH_HEAD + git -C "apps/erpnext" checkout -q -f FETCH_HEAD + git -C "apps/hrms" checkout -q -f "$GITHUB_SHA" + + pgrep honcho | xargs kill + rm -rf ~/frappe-bench/env + bench -v setup env + bench pip install -e ./apps/erpnext + bench pip install -e ./apps/hrms + bench start &>> ~/frappe-bench/bench_start.log & + + bench --site test_site migrate + + - name: Show bench output + if: ${{ always() }} + run: | + cd ~/frappe-bench + cat bench_start.log || true + cd logs + for f in ./*.log*; do + echo "Printing log: $f"; + cat $f + done \ No newline at end of file diff --git a/frontend/src/components/ExpenseAdvancesTable.vue b/frontend/src/components/ExpenseAdvancesTable.vue index 918d894384..16da1208e0 100644 --- a/frontend/src/components/ExpenseAdvancesTable.vue +++ b/frontend/src/components/ExpenseAdvancesTable.vue @@ -21,7 +21,7 @@
@@ -34,7 +34,7 @@
{{ __("{0}: {1}", [ __("Unclaimed Amount"), - formatCurrency(advance.unclaimed_amount, currency), + formatCurrency(advance.unclaimed_amount, expenseClaim.currency), ]) }}
@@ -74,17 +74,13 @@ const props = defineProps({ type: Object, required: true, }, - currency: { - type: String, - required: true, - }, isReadOnly: { type: Boolean, default: false, }, }) -const currencySymbol = computed(() => getCurrencySymbol(props.currency)) +const currencySymbol = computed(() => getCurrencySymbol(props.expenseClaim.currency)) function toggleAdvanceSelection(advance) { if (props.isReadOnly) return 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/components/ExpenseItems.vue b/frontend/src/components/ExpenseItems.vue index fbe22fd0ca..29ae4d0a2a 100644 --- a/frontend/src/components/ExpenseItems.vue +++ b/frontend/src/components/ExpenseItems.vue @@ -21,7 +21,7 @@ {{ __("{0}: {1}", [ __("Sanctioned"), - formatCurrency(item.sanctioned_amount || 0, currency), + formatCurrency(item.sanctioned_amount || 0, doc.currency), ]) }} @@ -33,7 +33,7 @@ - {{ formatCurrency(item.amount, currency) }} + {{ formatCurrency(item.amount, doc.currency) }} @@ -42,9 +42,8 @@ diff --git a/frontend/src/components/ExpenseTaxesTable.vue b/frontend/src/components/ExpenseTaxesTable.vue index 1bef2bc2a8..841d38ff11 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) }}
@@ -134,16 +134,13 @@ import EmptyState from "@/components/EmptyState.vue" import CustomIonModal from "@/components/CustomIonModal.vue" import { formatCurrency } from "@/utils/formatters" +import { useCurrencyConversion } from "@/composables/useCurrencyConversion" const props = defineProps({ expenseClaim: { type: Object, required: true, }, - currency: { - type: String, - required: true, - }, isReadOnly: { type: Boolean, default: false, @@ -215,6 +212,13 @@ const taxesTableFields = createResource({ }) taxesTableFields.reload() +const expenseClaimRef = computed(() => props.expenseClaim) +useCurrencyConversion( + taxesTableFields, + expenseClaimRef, + ["tax_amount", "total"] +) + const modalTitle = computed(() => { if (props.isReadOnly) return __("Expense Tax") diff --git a/frontend/src/components/ExpensesTable.vue b/frontend/src/components/ExpensesTable.vue index c12746d5cd..80b62eb921 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) }}
@@ -75,7 +75,7 @@
-
+
!excludeFields.includes(field.fieldname)) }, }) expensesTableFields.reload() +const expenseClaimRef = computed(() => props.expenseClaim) +useCurrencyConversion( + expensesTableFields, + expenseClaimRef, + ["amount", "sanctioned_amount"] +) + const modalTitle = computed(() => { if (props.isReadOnly) return __("Expense Item") diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index 0655a44055..e4b13ae79f 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -34,9 +34,19 @@ @update:modelValue="(v) => emit('update:modelValue', v)" /> + + diff --git a/frontend/src/components/ListView.vue b/frontend/src/components/ListView.vue index 77c9179d6b..e3dfc17dff 100644 --- a/frontend/src/components/ListView.vue +++ b/frontend/src/components/ListView.vue @@ -284,7 +284,7 @@ const documents = createResource({ const createPermission = createResource({ url: "frappe.client.has_permission", - params: { doctype: props.doctype, docname: null, perm_type: "create" }, + params: { doctype: props.doctype, docname: "", perm_type: "create" }, auto: true, }) diff --git a/frontend/src/composables/useCurrencyConversion.js b/frontend/src/composables/useCurrencyConversion.js new file mode 100644 index 0000000000..060c2e1bcc --- /dev/null +++ b/frontend/src/composables/useCurrencyConversion.js @@ -0,0 +1,41 @@ +import { watch } from "vue" + +export function useCurrencyConversion(formFields, docRef, fieldsToConvert = []) { + /** + * Accepts a formFields resource, a doc ref and an array of fieldnames which are currency fields and need to have the currency in their label + * Watches and updates the labels of the currency fields to include the currency in labels + */ + const currencyFields = new Set([...fieldsToConvert]) + + const updateLabels = () => { + formFields.data?.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 (currencyFields.has(field.fieldname)) { + field.label = `${field._original_label} (${docRef.value.currency})` + } + }) + } + + watch( + () => docRef.value?.currency, + () => { + updateLabels() + }, + { immediate: true } + ) + + watch( + () => formFields.data, + () => { + updateLabels() + }, + { deep: true, immediate: true } + ) + + return { updateLabels } +} \ 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 8697eff316..be6ec1f040 100644 --- a/frontend/src/views/employee_advance/Form.vue +++ b/frontend/src/views/employee_advance/Form.vue @@ -18,11 +18,10 @@ \ 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..e5f70eb9b2 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -13,12 +13,12 @@ :showAttachmentView="true" @validateForm="validateForm" :showDownloadPDFButton="true" + @formReloaded="onFormReloaded" > @@ -59,8 +56,8 @@ 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 { useCurrencyConversion } from "@/composables/useCurrencyConversion" const dayjs = inject("$dayjs") @@ -90,9 +87,10 @@ const tabs = [ const expenseClaim = ref({ employee: currEmployee, company: employeeCompany, + doctype: "Expense Claim", }) -const currency = computed(() => getCompanyCurrency(expenseClaim.value.company)) +const companyCurrency = computed(() => getCompanyCurrency(expenseClaim.value.company)) // get form fields const formFields = createResource({ @@ -108,46 +106,70 @@ const formFields = createResource({ }, onSuccess(_data) { expenseApproverDetails.reload() + if (!expenseClaim.value.currency) { + employeeCurrency.reload() + } companyDetails.reload() }, }) formFields.reload() -// resources +useCurrencyConversion( + formFields, + expenseClaim, + [ + "total_sanctioned_amount", + "total_taxes_and_charges", + "total_advance_amount", + "grand_total", + "total_claimed_amount" + ] +) + +// resources & helper functions const advances = createResource({ url: "hrms.hr.doctype.expense_claim.expense_claim.get_advances", - params: { employee: currEmployee.value }, - auto: true, + makeParams() { + return { expense_claim: expenseClaim.value } + }, onSuccess(data) { - // set advances - if (props.id) { - expenseClaim.value.advances?.map((advance) => (advance.selected = true)) - } else { - expenseClaim.value.advances = [] - } - - 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, - }) - }) + selectAllocatedAdvances() + addUnallocatedAdvances(data) }, }) +function selectAllocatedAdvances() { + if (props.id) { + expenseClaim.value?.advances?.map((advance) => (advance.selected = true)) + } else { + expenseClaim.value.advances = [] + } +} + +function addUnallocatedAdvances(data) { + // only show advances for selection in a draft claim + const isDraft = expenseClaim.value?.docstatus == 0 || !expenseClaim.value?.docstatus + if (!isDraft) return + + const allocatedAdvances = new Set( + expenseClaim.value?.advances?.map((advance) => advance.employee_advance) + ) + + return data.forEach((advance) => { + if (props.id && allocatedAdvances.has(advance.employee_advance)) return + + expenseClaim.value?.advances?.push({ + ...advance, + selected: false, + allocated_amount: 0, + }) + }) +} + +function onFormReloaded() { + advances.reload() +} + const expenseApproverDetails = createResource({ url: "hrms.api.get_expense_approval_details", params: { employee: currEmployee.value }, @@ -156,6 +178,22 @@ const expenseApproverDetails = createResource({ }, }) +const employeeCurrency = createResource({ + url: "frappe.client.get_value", + makeParams() { + return { + doctype: "Employee", + fieldname: ["salary_currency"], + filters: { name: currEmployee.value }, + }; + }, + onSuccess(data) { + if (data?.salary_currency) { + expenseClaim.value.currency = data.salary_currency; + } + } +}); + const companyDetails = createResource({ url: "hrms.api.get_company_cost_center_and_expense_account", params: { company: expenseClaim.value.company }, @@ -166,6 +204,13 @@ const companyDetails = createResource({ }, }) +const exchangeRate = createResource({ + url: "erpnext.setup.utils.get_exchange_rate", + onSuccess(data) { + expenseClaim.value.exchange_rate = data + }, +}) + // form scripts watch( () => expenseClaim.value.employee, @@ -176,8 +221,10 @@ watch( } currEmployee.value = employee_id expenseApproverDetails.fetch({ employee: currEmployee.value }) - } + employeeCurrency.fetch() + }, ) + watch( () => expenseClaim.value.company, (company) => { @@ -185,13 +232,18 @@ watch( companyDetails.fetch({ company: employeeCompany.value }) } ) + watch( - () => props.id && expenseClaim.value.expenses, - (_) => { - if (expenseClaim.value.docstatus === 0) { - advances.reload() - } - } + () => expenseClaim.value.currency, + () => setExchangeRate() +) + +watch( + () => expenseClaim.value.name, + () => { + advances.reload() + }, + { immediate: true } ) watch( @@ -235,7 +287,12 @@ function getFilteredFields(fields) { if (!props.id) excludeFields.push(...extraFields) - return fields.filter((field) => !excludeFields.includes(field.fieldname)) + return fields.filter((field) => { + if (excludeFields.includes(field.fieldname)) return false + + if (field.fieldname?.startsWith("base_")) return false + return true + }) } function applyFilters(field) { @@ -245,6 +302,7 @@ function applyFilters(field) { account_type: "Payable", company: expenseClaim.value.company, is_group: 0, + account_currency: expenseClaim.value.currency, } } else if (field.fieldname === "cost_center") { field.linkFilters = { @@ -364,6 +422,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) => { @@ -387,8 +447,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 @@ -413,4 +473,16 @@ function validateForm() { }) } +function setExchangeRate() { + if (!expenseClaim.value.currency || !formFields.data) return + const exchange_rate_field = formFields.data?.find( + (field) => field.fieldname === "exchange_rate" + ) + + 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/frontend/src/views/expense_claim/List.vue b/frontend/src/views/expense_claim/List.vue index 0edc3aa7e8..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", @@ -28,7 +29,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 = [ diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 41d2140853..c84c910dff 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -506,6 +506,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", @@ -656,11 +657,6 @@ def get_employee_advance_balance() -> list[dict]: return advances -@frappe.whitelist() -def get_advance_account(company: str) -> str | None: - return frappe.db.get_value("Company", company, "default_employee_advance_account", cache=True) - - # Company @frappe.whitelist() def get_company_currencies() -> dict: diff --git a/hrms/hr/doctype/expense_claim/expense_claim.js b/hrms/hr/doctype/expense_claim/expense_claim.js index 4b5737c129..f622ed3fa9 100644 --- a/hrms/hr/doctype/expense_claim/expense_claim.js +++ b/hrms/hr/doctype/expense_claim/expense_claim.js @@ -141,9 +141,9 @@ frappe.ui.form.on("Expense Claim", { }, currency: function (frm) { + frm.trigger("set_exchange_rate"); frm.trigger("update_fields_label"); frm.trigger("update_child_fields_label"); - frm.trigger("set_exchange_rate"); }, set_exchange_rate: function (frm) { @@ -459,7 +459,6 @@ frappe.ui.form.on("Expense Claim", { }, get_advances: function (frm) { - frappe.model.clear_table(frm.doc, "advances"); if (frm.doc.employee) { return frappe.call({ method: "hrms.hr.doctype.expense_claim.expense_claim.get_advances", @@ -467,6 +466,7 @@ frappe.ui.form.on("Expense Claim", { expense_claim: frm.doc, }, callback: function (r, rt) { + frappe.model.clear_table(frm.doc, "advances"); if (r.message) { $.each(r.message, function (i, d) { var row = frappe.model.add_child( diff --git a/roster/src/components/Link.vue b/roster/src/components/Link.vue index 3f6056dfe6..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 || ""); + } }, }); @@ -88,9 +90,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); @@ -104,4 +103,9 @@ watch( }, { immediate: true }, ); + +watch( + () => props.filters, + () => reloadOptions(""), +);