diff --git a/jey_erp/default_data/salary_components.json b/jey_erp/default_data/salary_components.json index bd64741..e192eb3 100644 --- a/jey_erp/default_data/salary_components.json +++ b/jey_erp/default_data/salary_components.json @@ -1,11 +1,11 @@ [ { - "salary_component": "İşsizlikdən sığorta haqqı (xərc şirkət 0,5%)", - "salary_component_abbr": "İŞ-XŞ", + "salary_component": "Keyfiim bonusu", + "salary_component_abbr": "KB", "type": "Earning", - "description": "İşsizlik, şirkətdən tutulan vergi", - "depends_on_payment_days": 0, - "is_tax_applicable": 0, + "description": null, + "depends_on_payment_days": 1, + "is_tax_applicable": 1, "deduct_full_tax_on_selected_payroll_date": 0, "variable_based_on_taxable_salary": 0, "is_income_tax_component": 0, @@ -13,50 +13,15 @@ "round_to_the_nearest_integer": 0, "statistical_component": 0, "accrual_component": 0, - "do_not_include_in_total": 1, - "do_not_include_in_accounts": 0, - "remove_if_zero_valued": 1, - "arrear_component": 0, - "disabled": 0, - "condition": "", - "amount": 0, - "amount_based_on_formula": 1, - "formula": "base * 0.5 / 100", - "is_flexible_benefit": 0, - "payout_method": "", - "final_cycle_accrual_payout": 0, - "max_benefit_amount": 0, - "doctype": "Salary Component", - "accounts": [ - { - "account": "İşçinin işsizlik vergisinin xərcləri", - "doctype": "Salary Component Account" - } - ] - }, - { - "salary_component": "İcbari tibbi sığorta (xərc şirkət 2%)", - "salary_component_abbr": "İTS-XŞ", - "type": "Earning", - "description": "İcbari tibbi sığorta tutulması, şirkətdən (2%)", - "depends_on_payment_days": 0, - "is_tax_applicable": 0, - "deduct_full_tax_on_selected_payroll_date": 0, - "variable_based_on_taxable_salary": 0, - "is_income_tax_component": 0, - "exempted_from_income_tax": 0, - "round_to_the_nearest_integer": 0, - "statistical_component": 0, - "accrual_component": 0, - "do_not_include_in_total": 1, + "do_not_include_in_total": 0, "do_not_include_in_accounts": 0, "remove_if_zero_valued": 1, "arrear_component": 0, "disabled": 0, "condition": null, "amount": 0, - "amount_based_on_formula": 1, - "formula": "base * 0.02 if base <= 8000 else 160 + (base - 8000) * 0.005", + "amount_based_on_formula": 0, + "formula": null, "is_flexible_benefit": 0, "payout_method": "", "final_cycle_accrual_payout": 0, @@ -64,11 +29,41 @@ "doctype": "Salary Component", "accounts": [ { - "account": "İcbari tibbi sığorta xərcləri", + "account": "İstehsal sahəsində çalışan işçilərin əmək haqqı xərcləri", "doctype": "Salary Component Account" } ] }, + { + "salary_component": "Gəlir vergisi (yalnız işçi)", + "salary_component_abbr": "GV_1", + "type": "Deduction", + "description": "Gəlir vergisi", + "depends_on_payment_days": 0, + "is_tax_applicable": 0, + "deduct_full_tax_on_selected_payroll_date": 0, + "variable_based_on_taxable_salary": 0, + "is_income_tax_component": 1, + "exempted_from_income_tax": 0, + "round_to_the_nearest_integer": 0, + "statistical_component": 0, + "accrual_component": 0, + "do_not_include_in_total": 0, + "do_not_include_in_accounts": 0, + "remove_if_zero_valued": 1, + "arrear_component": 0, + "disabled": 1, + "condition": null, + "amount": 0, + "amount_based_on_formula": 1, + "formula": "(\n (base - 200) * 0.03 if base <= 2500\n else (75 + (base - 2500) * 0.10 if base <= 8000\n else 625 + (base - 8000) * 0.14)\n)\nif getdate(start_date).year >= 2026\nelse 0", + "is_flexible_benefit": 0, + "payout_method": "", + "final_cycle_accrual_payout": 0, + "max_benefit_amount": 0, + "doctype": "Salary Component", + "accounts": [] + }, { "salary_component": "DSMF (xərc şirkət)", "salary_component_abbr": "DSMF-XŞ", @@ -87,7 +82,7 @@ "do_not_include_in_accounts": 0, "remove_if_zero_valued": 1, "arrear_component": 0, - "disabled": 0, + "disabled": 1, "condition": null, "amount": 0, "amount_based_on_formula": 1, @@ -97,9 +92,99 @@ "final_cycle_accrual_payout": 0, "max_benefit_amount": 0, "doctype": "Salary Component", + "accounts": [] + }, + { + "salary_component": "İcbari tibbi sığorta (xərc şirkət 2%)", + "salary_component_abbr": "İTS-XŞ", + "type": "Earning", + "description": "İcbari tibbi sığorta tutulması, şirkətdən (2%)", + "depends_on_payment_days": 0, + "is_tax_applicable": 0, + "deduct_full_tax_on_selected_payroll_date": 0, + "variable_based_on_taxable_salary": 0, + "is_income_tax_component": 0, + "exempted_from_income_tax": 0, + "round_to_the_nearest_integer": 0, + "statistical_component": 0, + "accrual_component": 0, + "do_not_include_in_total": 1, + "do_not_include_in_accounts": 0, + "remove_if_zero_valued": 1, + "arrear_component": 0, + "disabled": 1, + "condition": null, + "amount": 0, + "amount_based_on_formula": 1, + "formula": "base * 0.02 if base <= 8000 else 160 + (base - 8000) * 0.005", + "is_flexible_benefit": 0, + "payout_method": "", + "final_cycle_accrual_payout": 0, + "max_benefit_amount": 0, + "doctype": "Salary Component", + "accounts": [] + }, + { + "salary_component": "İşsizlikdən sığorta haqqı (xərc şirkət 0,5%)", + "salary_component_abbr": "İŞ-XŞ", + "type": "Earning", + "description": "İşsizlik, şirkətdən tutulan vergi", + "depends_on_payment_days": 0, + "is_tax_applicable": 0, + "deduct_full_tax_on_selected_payroll_date": 0, + "variable_based_on_taxable_salary": 0, + "is_income_tax_component": 0, + "exempted_from_income_tax": 0, + "round_to_the_nearest_integer": 0, + "statistical_component": 0, + "accrual_component": 0, + "do_not_include_in_total": 1, + "do_not_include_in_accounts": 0, + "remove_if_zero_valued": 1, + "arrear_component": 0, + "disabled": 1, + "condition": "", + "amount": 0, + "amount_based_on_formula": 1, + "formula": "base * 0.5 / 100", + "is_flexible_benefit": 0, + "payout_method": "", + "final_cycle_accrual_payout": 0, + "max_benefit_amount": 0, + "doctype": "Salary Component", + "accounts": [] + }, + { + "salary_component": "Məzuniyyət Kompensasiyası", + "salary_component_abbr": "LE", + "type": "Earning", + "description": "Məzuniyyət Kompensasiyası", + "depends_on_payment_days": 0, + "is_tax_applicable": 1, + "deduct_full_tax_on_selected_payroll_date": 0, + "variable_based_on_taxable_salary": 0, + "is_income_tax_component": 0, + "exempted_from_income_tax": 0, + "round_to_the_nearest_integer": 0, + "statistical_component": 0, + "accrual_component": 0, + "do_not_include_in_total": 0, + "do_not_include_in_accounts": 0, + "remove_if_zero_valued": 1, + "arrear_component": 0, + "disabled": 0, + "condition": null, + "amount": 0, + "amount_based_on_formula": 1, + "formula": "base * 12 / 365", + "is_flexible_benefit": 0, + "payout_method": "", + "final_cycle_accrual_payout": 0, + "max_benefit_amount": 0, + "doctype": "Salary Component", "accounts": [ { - "account": "Sosial sığorta xərcləri (DSMF Şirkət)", + "account": "İnzibati işçi heyəti xərcləri", "doctype": "Salary Component Account" } ] @@ -126,7 +211,7 @@ "condition": "", "amount": 0, "amount_based_on_formula": 1, - "formula": "base * 0.5 / 100", + "formula": "base * 0.005 + LE * 0.005", "is_flexible_benefit": 0, "payout_method": "", "final_cycle_accrual_payout": 0, @@ -161,7 +246,7 @@ "condition": "", "amount": 0, "amount_based_on_formula": 1, - "formula": "base * 0.5 / 100", + "formula": "base * 0.005 + LE * 0.005", "is_flexible_benefit": 0, "payout_method": "", "final_cycle_accrual_payout": 0, @@ -174,6 +259,111 @@ } ] }, + { + "salary_component": "İcbari tibbi sığorta (şirkət 2%)", + "salary_component_abbr": "İTS-Ş", + "type": "Deduction", + "description": "İcbari tibbi sığorta tutulması, şirkətdən (2%)", + "depends_on_payment_days": 0, + "is_tax_applicable": 0, + "deduct_full_tax_on_selected_payroll_date": 0, + "variable_based_on_taxable_salary": 0, + "is_income_tax_component": 0, + "exempted_from_income_tax": 0, + "round_to_the_nearest_integer": 0, + "statistical_component": 0, + "accrual_component": 0, + "do_not_include_in_total": 1, + "do_not_include_in_accounts": 0, + "remove_if_zero_valued": 1, + "arrear_component": 0, + "disabled": 0, + "condition": null, + "amount": 0, + "amount_based_on_formula": 1, + "formula": "(base * 0.02 if base <= 2500 else 50 + (base - 2500) * 0.005) + (LE * 0.02 if LE <= 2500 else 50 + (LE - 2500) * 0.005)", + "is_flexible_benefit": 0, + "payout_method": "", + "final_cycle_accrual_payout": 0, + "max_benefit_amount": 0, + "doctype": "Salary Component", + "accounts": [ + { + "account": "İcbari tibbi sığorta (şirkət)", + "doctype": "Salary Component Account" + } + ] + }, + { + "salary_component": "İcbari tibbi sığorta (işçi 2%)", + "salary_component_abbr": "İTS-İ", + "type": "Deduction", + "description": "İcbari tibbi sığorta tutulması, işçidən (2%)", + "depends_on_payment_days": 0, + "is_tax_applicable": 0, + "deduct_full_tax_on_selected_payroll_date": 0, + "variable_based_on_taxable_salary": 0, + "is_income_tax_component": 0, + "exempted_from_income_tax": 0, + "round_to_the_nearest_integer": 0, + "statistical_component": 0, + "accrual_component": 0, + "do_not_include_in_total": 0, + "do_not_include_in_accounts": 0, + "remove_if_zero_valued": 1, + "arrear_component": 0, + "disabled": 0, + "condition": "", + "amount": 0, + "amount_based_on_formula": 1, + "formula": "(base * 0.02 if base <= 2500 else 50 + (base - 2500) * 0.005) + (LE * 0.02 if LE <= 2500 else 50 + (LE - 2500) * 0.005)", + "is_flexible_benefit": 0, + "payout_method": "", + "final_cycle_accrual_payout": 0, + "max_benefit_amount": 0, + "doctype": "Salary Component", + "accounts": [ + { + "account": "İcbari tibbi sığorta (işçi)", + "doctype": "Salary Component Account" + } + ] + }, + { + "salary_component": "Gəlir vergisi", + "salary_component_abbr": "GV", + "type": "Deduction", + "description": "Gəlir vergisi", + "depends_on_payment_days": 0, + "is_tax_applicable": 0, + "deduct_full_tax_on_selected_payroll_date": 0, + "variable_based_on_taxable_salary": 0, + "is_income_tax_component": 1, + "exempted_from_income_tax": 0, + "round_to_the_nearest_integer": 0, + "statistical_component": 0, + "accrual_component": 0, + "do_not_include_in_total": 0, + "do_not_include_in_accounts": 0, + "remove_if_zero_valued": 1, + "arrear_component": 0, + "disabled": 0, + "condition": null, + "amount": 0, + "amount_based_on_formula": 1, + "formula": "((base - 200) * 0.03 if base <= 2500 else 75 + (base - 2500) * 0.10 if base <= 8000 else 625 + (base - 8000) * 0.14) + (LE * 0.03 if LE <= 2500 else 75 + (LE - 2500) * 0.10 if LE <= 8000 else 625 + (LE - 8000) * 0.14)", + "is_flexible_benefit": 0, + "payout_method": "", + "final_cycle_accrual_payout": 0, + "max_benefit_amount": 0, + "doctype": "Salary Component", + "accounts": [ + { + "account": "Gəlir vergisi (işçi)", + "doctype": "Salary Component Account" + } + ] + }, { "salary_component": "Əsas əmək haqqı", "salary_component_abbr": "ƏH", @@ -204,42 +394,7 @@ "doctype": "Salary Component", "accounts": [ { - "account": "İşçilərin əmək haqqı xərcləri", - "doctype": "Salary Component Account" - } - ] - }, - { - "salary_component": "İcbari tibbi sığorta (şirkət 2%)", - "salary_component_abbr": "İTS-Ş", - "type": "Deduction", - "description": "İcbari tibbi sığorta tutulması, şirkətdən (2%)", - "depends_on_payment_days": 0, - "is_tax_applicable": 0, - "deduct_full_tax_on_selected_payroll_date": 0, - "variable_based_on_taxable_salary": 0, - "is_income_tax_component": 0, - "exempted_from_income_tax": 0, - "round_to_the_nearest_integer": 0, - "statistical_component": 0, - "accrual_component": 0, - "do_not_include_in_total": 1, - "do_not_include_in_accounts": 0, - "remove_if_zero_valued": 1, - "arrear_component": 0, - "disabled": 0, - "condition": null, - "amount": 0, - "amount_based_on_formula": 1, - "formula": "base * 0.02 if base <= 8000 else 160 + (base - 8000) * 0.005", - "is_flexible_benefit": 0, - "payout_method": "", - "final_cycle_accrual_payout": 0, - "max_benefit_amount": 0, - "doctype": "Salary Component", - "accounts": [ - { - "account": "İcbari tibbi sığorta (şirkət)", + "account": "İnzibati işçi heyəti xərcləri", "doctype": "Salary Component Account" } ] @@ -266,7 +421,7 @@ "condition": null, "amount": 0, "amount_based_on_formula": 1, - "formula": "200 * 0.22 if base <= 200 else 44 + (base - 200) * 0.15", + "formula": "(base * 0.22 if base <= 200 else 44 + (base - 200) * 0.15 if base <= 8000 else 1214 + (base - 8000) * 0.11) + (LE * 0.22 if LE <= 200 else 44 + (LE - 200) * 0.15 if LE <= 8000 else 1214 + (LE - 8000) * 0.11)", "is_flexible_benefit": 0, "payout_method": "", "final_cycle_accrual_payout": 0, @@ -274,77 +429,7 @@ "doctype": "Salary Component", "accounts": [ { - "account": "Sosial sığorta öhdəlikləri (DSMF Şirkət)", - "doctype": "Salary Component Account" - } - ] - }, - { - "salary_component": "Gəlir vergisi (yalnız işçi)", - "salary_component_abbr": "GV", - "type": "Deduction", - "description": "Gəlir vergisi", - "depends_on_payment_days": 0, - "is_tax_applicable": 0, - "deduct_full_tax_on_selected_payroll_date": 0, - "variable_based_on_taxable_salary": 0, - "is_income_tax_component": 1, - "exempted_from_income_tax": 0, - "round_to_the_nearest_integer": 0, - "statistical_component": 0, - "accrual_component": 0, - "do_not_include_in_total": 0, - "do_not_include_in_accounts": 0, - "remove_if_zero_valued": 1, - "arrear_component": 0, - "disabled": 0, - "condition": null, - "amount": 0, - "amount_based_on_formula": 1, - "formula": "(\n (base - 200) * 0.03 if base <= 2500\n else (75 + (base - 2500) * 0.10 if base <= 8000\n else 625 + (base - 8000) * 0.14)\n)\nif getdate(start_date).year >= 2026\nelse 0", - "is_flexible_benefit": 0, - "payout_method": "", - "final_cycle_accrual_payout": 0, - "max_benefit_amount": 0, - "doctype": "Salary Component", - "accounts": [ - { - "account": "Gəlir vergisi (işçi)", - "doctype": "Salary Component Account" - } - ] - }, - { - "salary_component": "İcbari tibbi sığorta (işçi 2%)", - "salary_component_abbr": "İTS-İ", - "type": "Deduction", - "description": "İcbari tibbi sığorta tutulması, işçidən (2%)", - "depends_on_payment_days": 0, - "is_tax_applicable": 0, - "deduct_full_tax_on_selected_payroll_date": 0, - "variable_based_on_taxable_salary": 0, - "is_income_tax_component": 0, - "exempted_from_income_tax": 0, - "round_to_the_nearest_integer": 0, - "statistical_component": 0, - "accrual_component": 0, - "do_not_include_in_total": 0, - "do_not_include_in_accounts": 0, - "remove_if_zero_valued": 1, - "arrear_component": 0, - "disabled": 0, - "condition": "", - "amount": 0, - "amount_based_on_formula": 1, - "formula": "base * 0.02 if base <= 8000 else 160 + (base - 8000) * 0.005", - "is_flexible_benefit": 0, - "payout_method": "", - "final_cycle_accrual_payout": 0, - "max_benefit_amount": 0, - "doctype": "Salary Component", - "accounts": [ - { - "account": "İcbari tibbi sığorta (işçi)", + "account": "Sosial sığorta öhdəlikləri (DSMF şirkət)", "doctype": "Salary Component Account" } ] @@ -371,7 +456,7 @@ "condition": "", "amount": 0, "amount_based_on_formula": 1, - "formula": "200 * 0.03 if base <= 200 else 6 + (base - 200) * 0.10", + "formula": "(base * 0.03 if base <= 200 else 6 + (base - 200) * 0.10 if base <= 8000 else 786 + (base - 8000) * 0.10) + (LE * 0.03 if LE <= 200 else 6 + (LE - 200) * 0.10 if LE <= 8000 else 786 + (LE - 8000) * 0.10)", "is_flexible_benefit": 0, "payout_method": "", "final_cycle_accrual_payout": 0, @@ -383,40 +468,5 @@ "doctype": "Salary Component Account" } ] - }, - { - "salary_component": "Məzuniyyət Kompensasiyası", - "salary_component_abbr": "LE", - "type": "Earning", - "description": "Məzuniyyət Kompensasiyası", - "depends_on_payment_days": 0, - "is_tax_applicable": 1, - "deduct_full_tax_on_selected_payroll_date": 0, - "variable_based_on_taxable_salary": 0, - "is_income_tax_component": 0, - "exempted_from_income_tax": 0, - "round_to_the_nearest_integer": 0, - "statistical_component": 0, - "accrual_component": 0, - "do_not_include_in_total": 0, - "do_not_include_in_accounts": 0, - "remove_if_zero_valued": 1, - "arrear_component": 0, - "disabled": 1, - "condition": null, - "amount": 0, - "amount_based_on_formula": 1, - "formula": "base * 12 / 365", - "is_flexible_benefit": 0, - "payout_method": "", - "final_cycle_accrual_payout": 0, - "max_benefit_amount": 0, - "doctype": "Salary Component", - "accounts": [ - { - "account": "Əməyin ödənişi üzrə işçi heyətinə olan borclar", - "doctype": "Salary Component Account" - } - ] } ] \ No newline at end of file diff --git a/jey_erp/hooks.py b/jey_erp/hooks.py index a763cba..cc52694 100644 --- a/jey_erp/hooks.py +++ b/jey_erp/hooks.py @@ -112,6 +112,11 @@ def after_migrate_combined(): rebuild_tree("Tax Free Indicator") except Exception as e: print(f"Tax Free Indicator NSM rebuild failed: {e}") + from jey_erp.setup.setup_wizard_handler import sync_salary_components + try: + sync_salary_components() + except Exception as e: + print(f"Salary Component sync failed: {e}") # REMOVED: Asset categories creation moved to setup_wizard_complete hook # from jey_erp.custom.create_asset_categories import create_asset_categories # create_asset_categories() diff --git a/jey_erp/setup/setup_wizard_handler.py b/jey_erp/setup/setup_wizard_handler.py index 5aebdea..1f223fe 100644 --- a/jey_erp/setup/setup_wizard_handler.py +++ b/jey_erp/setup/setup_wizard_handler.py @@ -66,6 +66,100 @@ def create_default_salary_components(args): frappe.db.commit() +SC_SYNC_FIELDS = ( + "salary_component_abbr", "type", "description", + "depends_on_payment_days", "is_tax_applicable", + "deduct_full_tax_on_selected_payroll_date", + "variable_based_on_taxable_salary", "is_income_tax_component", + "exempted_from_income_tax", "round_to_the_nearest_integer", + "statistical_component", "accrual_component", + "do_not_include_in_total", "do_not_include_in_accounts", + "remove_if_zero_valued", "arrear_component", "disabled", + "condition", "amount", "amount_based_on_formula", "formula", + "is_flexible_benefit", "payout_method", + "final_cycle_accrual_payout", "max_benefit_amount", +) + + +def sync_salary_components(): + """Полная синхронизация Salary Component с default_data/salary_components.json. + JSON — single source of truth: создаёт отсутствующие, обновляет существующие, + удаляет лишние (не в JSON). Если удалить нельзя из-за linked records — + компонент помечается disabled. Идемпотентна, безопасна для повторного запуска. + Запускается автоматически из after_migrate. + """ + if not os.path.exists(SALARY_COMPONENTS_JSON): + return + + with open(SALARY_COMPONENTS_JSON, encoding="utf-8") as f: + components = json.load(f) + + companies = frappe.get_all("Company", pluck="name") + + def resolve_accounts(json_accounts): + out = [] + for row in json_accounts or []: + account_name = row.get("account") + if not account_name: + continue + for company in companies: + full = frappe.db.get_value( + "Account", + {"account_name": account_name, "company": company, "is_group": 0}, + "name", + ) + if full: + out.append({"company": company, "account": full}) + return out + + json_names = set() + for data in components: + name = data.get("salary_component") + if not name: + continue + json_names.add(name) + accounts = resolve_accounts(data.get("accounts")) + + if frappe.db.exists("Salary Component", name): + doc = frappe.get_doc("Salary Component", name) + for field in SC_SYNC_FIELDS: + if field in data: + doc.set(field, data[field]) + doc.set("accounts", []) + for acc in accounts: + doc.append("accounts", acc) + doc.save(ignore_permissions=True) + else: + new_data = {k: v for k, v in data.items() if k != "accounts"} + new_data["doctype"] = "Salary Component" + new_data["accounts"] = accounts + frappe.get_doc(new_data).insert(ignore_permissions=True) + + # Удаляем то, чего больше нет в JSON. Если удалить нельзя — disable. + for name in frappe.get_all("Salary Component", pluck="name"): + if name in json_names: + continue + try: + frappe.delete_doc("Salary Component", name, ignore_permissions=True) + except Exception: + try: + doc = frappe.get_doc("Salary Component", name) + doc.disabled = 1 + doc.save(ignore_permissions=True) + frappe.log_error( + f"Cannot delete Salary Component '{name}' (linked records exist). " + f"Marked as disabled.", + "Salary Component Sync", + ) + except Exception as inner: + frappe.log_error( + f"Failed to disable Salary Component '{name}': {inner}", + "Salary Component Sync", + ) + + frappe.db.commit() + + def create_default_leave_types(args): if not os.path.exists(LEAVE_TYPES_JSON): return