diff --git a/.github/workflows/review-translation-changes.yaml b/.github/workflows/review-translation-changes.yaml new file mode 100644 index 0000000000..df360a9a83 --- /dev/null +++ b/.github/workflows/review-translation-changes.yaml @@ -0,0 +1,25 @@ +name: Review translation PRs +description: "Posts review comments with relevant translation changes that are hard to inspect in the diff view." + +on: + pull_request_target: + types: [opened, reopened, synchronize, ready_for_review] + paths: + - "**/*.po" + - "**/*.pot" + +concurrency: + group: po-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + review-po-pr: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + issues: write + pull-requests: write + + steps: + - uses: alyf-de/po-review-action@ffb92a299452595ca50fb1429ee98a7ebacb571e # v1.1.0 diff --git a/hrms/__init__.py b/hrms/__init__.py index 543ad0c232..43db4a43e0 100644 --- a/hrms/__init__.py +++ b/hrms/__init__.py @@ -1,3 +1,6 @@ +import functools +import inspect + import frappe __version__ = "17.0.0-dev" @@ -10,3 +13,42 @@ def refetch_resource(cache_key: str | list, user=None): user=user or frappe.session.user, after_commit=True, ) + + +def get_region(company=None): + """Return the country used to resolve regional overrides, based on flag, + company or global settings. Mirrors erpnext.get_region. + + You can also set the global company flag in `frappe.flags.company`. + """ + if not company: + company = frappe.local.flags.company + + if company: + return frappe.get_cached_value("Company", company, "country") + + return frappe.flags.country or frappe.get_system_settings("country") + + +def allow_regional(fn): + """Decorator to make a function regionally overridable. Mirrors + erpnext.allow_regional so HRMS functions can be overridden per region via the + `regional_overrides` hook. + + Example: + @hrms.allow_regional + def myfunction(): + pass""" + + @functools.wraps(fn) + def caller(*args, **kwargs): + overrides = frappe.get_hooks("regional_overrides", {}).get(get_region()) + function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}" + + if not overrides or function_path not in overrides: + return fn(*args, **kwargs) + + # Priority given to last installed app + return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs) + + return caller diff --git a/hrms/overrides/company.py b/hrms/overrides/company.py index 2636d51f71..4a21f26c5b 100644 --- a/hrms/overrides/company.py +++ b/hrms/overrides/company.py @@ -83,9 +83,9 @@ def make_salary_components(country): doc.flags.ignore_permissions = True doc.flags.ignore_mandatory = True doc.insert(ignore_if_duplicate=True) - except frappe.NameError: - frappe.clear_messages() - except frappe.DuplicateEntryError: + except Exception as e: + frappe.error_log("Error occurred while creating Salary Component", e) + finally: frappe.clear_messages() diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.py b/hrms/payroll/doctype/salary_slip/salary_slip.py index 230a87b396..73d6acf4ac 100644 --- a/hrms/payroll/doctype/salary_slip/salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/salary_slip.py @@ -31,6 +31,7 @@ from erpnext.setup.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase +import hrms from hrms.hr.utils import validate_active_employee from hrms.payroll.doctype.additional_salary.additional_salary import get_additional_salaries from hrms.payroll.doctype.employee_benefit_ledger.employee_benefit_ledger import ( @@ -941,11 +942,21 @@ def set_gross_pay_and_base_gross_pay(): set_loan_repayment(self) + # Region-specific deductions (e.g. India statutory deductions) are injected + # here so they are reflected in both saved slips and the preview generated + # by process_salary_structure, before totals are finalised below. + self.apply_regional_deductions() + self.set_precision_for_component_amounts() self.set_net_pay() if not skip_tax_breakup_computation: self.compute_income_tax_breakup() + @hrms.allow_regional + def apply_regional_deductions(self): + "Hook point for region-specific salary slip deductions." + pass + def set_net_pay(self): self.total_deduction = self.get_component_totals("deductions") self.base_total_deduction = flt(