jey_erp/jey_erp/custom_fields.py

2122 lines
92 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import frappe
from frappe.custom.doctype.custom_field.custom_field import (
create_custom_field,
get_existing_custom_fields,
)
def _apply_custom_fields(custom_fields: dict, ignore_validate: bool = False) -> None:
"""Idempotent bulk create/update for Custom Fields.
Reimplements frappe.custom...create_custom_fields with two differences:
- saves with ignore_version=True to skip Frappe's version diff (which has
a bug formatting some int values for Custom Field), and
- calls frappe.db.updatedb() once per affected doctype, only when needed.
"""
try:
frappe.flags.in_create_custom_fields = True
existing = get_existing_custom_fields(custom_fields)
doctypes_to_update = set()
for doctype, fields in custom_fields.items():
updated = False
for df in fields:
key = (doctype, df["fieldname"])
field = existing.get(key)
if not field:
try:
df = df.copy()
df["owner"] = "Administrator"
cf = create_custom_field(doctype, df, ignore_validate=ignore_validate)
if cf:
existing[key] = cf.__dict__
updated = True
except frappe.exceptions.DuplicateEntryError:
pass
continue
cf = frappe.get_doc({"doctype": "Custom Field", **field})
original = cf.__dict__.copy()
cf.update(df)
if original == cf.__dict__:
continue
cf.flags.ignore_validate = ignore_validate
# ignore_version must be passed as kwarg: _save() overwrites
# self.flags.ignore_version from this argument on every call.
cf.save(ignore_version=True)
existing[key] = cf.__dict__
updated = True
if updated:
doctypes_to_update.add(doctype)
for doctype in doctypes_to_update:
frappe.clear_cache(doctype=doctype)
frappe.db.updatedb(doctype)
finally:
frappe.flags.in_create_custom_fields = False
def _apply_property_setters(property_setters: list[dict]) -> None:
"""Idempotent upsert for Property Setters.
Each entry: {doctype, fieldname, property, value, property_type, for_doctype?}.
Set `for_doctype=True` for DocType-level properties (e.g. field_order)
where `fieldname` is ignored. Reuses Frappe's make_property_setter
(idempotent on (doc_type, field_name, property)) and pre-checks the
existing value to avoid version churn when nothing changed.
"""
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
affected_doctypes: set[str] = set()
for ps in property_setters:
doctype = ps["doctype"]
prop = ps["property"]
value = ps["value"]
property_type = ps.get("property_type", "Data")
for_doctype = bool(ps.get("for_doctype", False))
fieldname = ps.get("fieldname") or ""
ps_filters = {"doc_type": doctype, "property": prop}
if for_doctype:
ps_filters["doctype_or_field"] = "DocType"
else:
ps_filters["field_name"] = fieldname
ps_filters["doctype_or_field"] = "DocField"
existing_value = frappe.db.get_value("Property Setter", ps_filters, "value")
if existing_value is not None and str(existing_value) == str(value):
continue
try:
make_property_setter(
doctype,
fieldname,
prop,
value,
property_type,
for_doctype=for_doctype,
validate_fields_for_doctype=False,
)
affected_doctypes.add(doctype)
except Exception as e:
target = doctype if for_doctype else f"{doctype}.{fieldname}"
print(f"Failed to set {prop}={value} on {target}: {e}")
for doctype in affected_doctypes:
frappe.clear_cache(doctype=doctype)
def create_custom_fields():
custom_fields = {
"Asset": [
# Asset Category checkboxes
dict(
fieldname='land',
label='Land',
fieldtype='Check',
insert_after='asset_name',
reqd=0,
in_list_view=1,
columns=3
),
dict(
fieldname='property',
label='Property',
fieldtype='Check',
insert_after='land',
reqd=0,
in_list_view=1,
columns=3
),
# Main Asset Type field (initially hidden)
dict(
fieldname='custom_asset_type',
label='Asset Type',
fieldtype='Select',
options='', # Options will be set dynamically
insert_after='property',
reqd=0,
hidden=1,
columns=6
),
# === AGRICULTURAL LAND SECTION ===
dict(
fieldname='agricultural_land_section',
label='Agricultural Land Information',
fieldtype='Section Break',
insert_after='custom_asset_type',
collapsible=1,
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"'
),
dict(
fieldname='agricultural_purpose',
label='Purpose',
fieldtype='Link',
options='Agricultural Purpose Type',
insert_after='agricultural_land_section',
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"',
reqd=0
),
dict(
fieldname='cadastral_district_name',
label='Name of the Cadastral Valuation District',
fieldtype='Link',
options='Cadastral Valuation District',
insert_after='agricultural_purpose',
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"',
reqd=0
),
dict(
fieldname='cadastral_district_code',
label='Code of the Cadastral Valuation District',
fieldtype='Int',
insert_after='cadastral_district_name',
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"',
reqd=0
),
dict(
fieldname='quality_groups',
label='Quality Groups',
fieldtype='Select',
options='\nI\nII\nIII\nIV\nV',
insert_after='cadastral_district_code',
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"',
reqd=0
),
dict(
fieldname='agricultural_land_purpose',
label='Purpose of the Land Plot',
fieldtype='Select',
options='\nƏkin, dinc və çoxillik əkmələr\nBiçənək, örüş və otlaqlar',
insert_after='quality_groups',
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"',
reqd=0
),
# Column Break для размещения справа
dict(
fieldname='agricultural_column_break',
fieldtype='Column Break',
insert_after='agricultural_land_purpose',
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"'
),
# Tax Article for Agricultural Land
dict(
fieldname='agricultural_tax_article',
label='Tax Article',
fieldtype='Link',
options='Tax Article',
insert_after='agricultural_column_break',
depends_on='eval:doc.custom_asset_type == "Kənd təsərrüfatı təyinatlı torpaq sahələri"',
reqd=0
),
# Agricultural Validity HTML (прямо под tax article, БЕЗ секции)
dict(
fieldname='agricultural_validity_html',
fieldtype='HTML',
insert_after='agricultural_tax_article',
depends_on='eval:doc.agricultural_tax_article'
),
# === INDUSTRIAL/COMMERCIAL LAND SECTION ===
dict(
fieldname='industrial_land_section',
label='Industrial/Commercial Land Information',
fieldtype='Section Break',
insert_after='agricultural_validity_html',
collapsible=1,
depends_on='eval:doc.custom_asset_type == "Sənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xidməti və digər xüsusi təyinatlı torpaq sahələri"'
),
dict(
fieldname='territorial_unit_name',
label='Name of the Territorial Unit',
fieldtype='Select',
options='\nBakı\nGəncə, Sumqayıt, Xırdalan, Abşeron\nDigər şəhərlər (rayon tabeliyində olan şəhərlər istisna olmaqla), Rayon mərkəzləri\nRayon tabeçiliyində olan şəhərlərdə, qəsəbələrdə və kəndlərdə (Bakı və Sumqayıt şəhərlərinin qəsəbə və kəndləri istisna olmaqla)',
insert_after='industrial_land_section',
depends_on='eval:doc.custom_asset_type == "Sənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xidməti və digər xüsusi təyinatlı torpaq sahələri"',
reqd=0
),
dict(
fieldname='territorial_unit_code',
label='Code of the Territorial Unit',
fieldtype='Data',
insert_after='territorial_unit_name',
depends_on='eval:doc.custom_asset_type == "Sənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xidməti və digər xüsusi təyinatlı torpaq sahələri"'
),
dict(
fieldname='industrial_land_purpose',
label='Purpose of the Land Plot',
fieldtype='Select',
options='\nSənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xid-ti və dig. xüs. təy-tlı sahələr\nYaşayış fondları, həyətyanı və bağsahələri üzrə sahələr',
insert_after='territorial_unit_code',
depends_on='eval:doc.custom_asset_type == "Sənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xidməti və digər xüsusi təyinatlı torpaq sahələri"',
reqd=0
),
dict(
fieldname='mining_note',
label='Note on Mining',
fieldtype='Check',
insert_after='industrial_land_purpose',
depends_on='eval:doc.custom_asset_type == "Sənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xidməti və digər xüsusi təyinatlı torpaq sahələri"'
),
# Column Break для размещения справа
dict(
fieldname='industrial_column_break',
fieldtype='Column Break',
insert_after='mining_note',
depends_on='eval:doc.custom_asset_type == "Sənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xidməti və digər xüsusi təyinatlı torpaq sahələri"'
),
# Tax Article for Industrial Land
dict(
fieldname='industrial_tax_article',
label='Tax Article',
fieldtype='Link',
options='Tax Article',
insert_after='industrial_column_break',
depends_on='eval:doc.custom_asset_type == "Sənaye, tikinti, nəqliyyat, rabitə, ticarət-məişət xidməti və digər xüsusi təyinatlı torpaq sahələri"',
reqd=0
),
# Industrial Validity HTML (прямо под tax article, БЕЗ секции)
dict(
fieldname='industrial_validity_html',
fieldtype='HTML',
insert_after='industrial_tax_article',
depends_on='eval:doc.industrial_tax_article'
),
# === TAX EXEMPT ASSETS SECTION ===
dict(
fieldname='tax_exempt_assets_section',
label='Tax Exempt Assets Information',
fieldtype='Section Break',
insert_after='industrial_validity_html',
collapsible=1,
depends_on='eval:doc.custom_asset_type == "Vergidən azad olunan əsas vəsaitlər"'
),
dict(
fieldname='tax_exempt_tax_article',
label='Tax Article',
fieldtype='Link',
options='Tax Article',
insert_after='tax_exempt_assets_section',
depends_on='eval:doc.custom_asset_type == "Vergidən azad olunan əsas vəsaitlər"',
reqd=0
),
# Tax Exempt Validity HTML (прямо под tax article, БЕЗ секции)
dict(
fieldname='tax_exempt_validity_html',
fieldtype='HTML',
insert_after='tax_exempt_tax_article',
depends_on='eval:doc.tax_exempt_tax_article'
),
dict(
fieldname='tax_exempt_settlements',
label='Settlements',
fieldtype='Select',
options='\nBakı\nGəncə, Sumqayıt şəhərləri və Abşeron rayonu\nDigər şəhərlər, rayon mərkəzləri\nRayon tabeliyində olan şəhərlərdə, qəsəbələrdə və kəndlərdə',
insert_after='tax_exempt_validity_html',
depends_on='eval:doc.custom_asset_type == "Vergidən azad olunan əsas vəsaitlər"',
reqd=0
),
# === TAXABLE ASSETS SECTION ===
dict(
fieldname='taxable_assets_section',
label='Taxable Assets Information',
fieldtype='Section Break',
insert_after='tax_exempt_settlements',
collapsible=1,
depends_on='eval:doc.custom_asset_type == "Vergiyə cəlb olunan əsas vəsaitlər"'
),
dict(
fieldname='taxable_settlements',
label='Settlements',
fieldtype='Select',
options='\nBakı\nGəncə, Sumqayıt şəhərləri və Abşeron rayonu\nDigər şəhərlər, rayon mərkəzləri\nRayon tabeliyində olan şəhərlərdə, qəsəbələrdə və kəndlərdə',
insert_after='taxable_assets_section',
depends_on='eval:doc.custom_asset_type == "Vergiyə cəlb olunan əsas vəsaitlər"',
reqd=0
),
dict(
fieldname='taxable_asset_type',
label='Taxable Asset Type',
fieldtype='Select',
options='\nÇoxmərtəbəli (çoxmənzilli) yaşayış binalar\nQeyri-yaşayış binaları (sahələri)\nƏmlak kompleksi kimi müəssisələr\nQurğular\nMənzillər\nFərdi yaşayış və bağ evləri\nMaşınlar və avadanlıqlar\nYüksək texnologiyalar məhsulu olan hesablama texnikası\nNəqliyyat vasitələri\nDigər əsas vəsaitlər',
insert_after='taxable_settlements',
depends_on='eval:doc.custom_asset_type == "Vergiyə cəlb olunan əsas vəsaitlər"',
reqd=0
),
# === COMMON SECTION (ALWAYS VISIBLE) ===
dict(
fieldname='common_land_section',
label='Common Information',
fieldtype='Section Break',
insert_after='taxable_asset_type',
collapsible=1
),
dict(
fieldname='tax_exempt_area',
label='Tax-exempt Area',
fieldtype='Data',
insert_after='common_land_section',
description='Enter the tax-exempt area value'
),
dict(
fieldname='area',
label='Area',
fieldtype='Float',
insert_after='tax_exempt_area',
description='For agricultural: hectares. For industrial: square meters.',
precision=4
)
],
"Payment Entry": [
dict(
fieldname='goods_provided_instead_of_salary',
label='Goods provided instead of salary',
fieldtype='Check',
insert_after='payment_type'
),
dict(
fieldname='custom_note',
label='Note',
fieldtype='Data',
insert_after='status'
),
dict(
fieldname='purpose',
label='Purpose',
fieldtype='Small Text',
insert_after='payment_type',
),
],
"Purchase Order": [
dict(
fieldname='is_taxes_doc',
label='Is taxes document',
fieldtype='Check',
insert_after='is_subcontracted',
hidden=1
),
dict(
fieldname='taxes_doc',
label='taxes_doc',
fieldtype='Link',
options='E-Taxes Purchase',
insert_after='is_taxes_doc',
hidden=1
),
# Purchase type fields
dict(
fieldname='purchase_type',
label='Purchase Type',
fieldtype='Check',
insert_after='taxes_doc',
default=0
),
dict(
fieldname='act_type',
label='Act Type',
fieldtype='Select',
options='\nPurchase Act\nEQF\nImport',
insert_after='purchase_type',
depends_on='eval:doc.purchase_type'
),
dict(
fieldname='agricultural_country',
label='Country',
fieldtype='Select',
options='\nAruba\nƏfqanıstan\nAnqola\nAngilya\nAlbaniya\nAndorra\nNiderland Antil adaları\nBƏƏ\nArgentina\nErmənistan\nAmerika Samoası\nAntiqua və Barbuda\nAvstraliya\nAvstriya\nBurundi\nBelçika\nBenin\nBurkina-Faso\nBanqladeş\nBolqarıstan\nBəhreyn\nBaham adaları\nBosniya və Herseqovina\nBelarus\nBeliz\nBermud adaları\nBoliviya\nBraziliya\nBarbados\nBruney\nButan\nBotsvana\nMAR\nKanada\nKokos adaları\nİsveçrə\nÇili\nÇin\nKot-d\'ivuar\nKamerun\nKDR\nKonqo Respublikası\nKuk adaları\nKolumbiya\nKomor adaları\nKabo-Verde\nKosta-Rika\nKuba\nMilad adası\nKayman adaları\nKipr\nÇexiya\nAlmaniya\nCibuti\nDominika\nDanimarka\nDominikan Respublikası\nƏlcəzair\nEkvador\nMisir\nEritreya\nQərbi Sahara\nİspaniya\nEstoniya\nEfiopiya\nFinlandiya\nFici\nFoklend adaları\nFransa\nFarer adaları\nMikroneziya\nQabon\nBöyük Britaniya\nGürcüstan\nQana\nCəbəli-Tariq\nQvineya\nQambiya\nQvineya-Bisau\nEkvatorial Qvineya\nYunanıstan\nQrenada\nQrenlandiya\nQvatemala\nQuam\nQayana\nHonkonq\nHonduras\nXorvatiya\nHaiti\nMacarıstan\nindoneziya\nHindistan\nirlandiya\nİran\nİraq\nislandiya\nisrail\nitaliya\nYamayka\nlordaniya\nYaponiya\nQazağıstan\nKeniya\nQıızıstan\nKamboca\nKiribati\nSent-Kits və Nevis\nCənubi Koreya\nKüveyt\nLaos\nLivan\nLiberiya\nLiviya\nSent-Lüsiya\nLixtenşteyn\nŞri-Lanka\nLesoto\nLitva\nLüksemburq\nLatviya\nMakao\nMərakeş\nMonako\nMoldova\nMadaqaskar\nMaldiv adaları\nMeksika\nMarşall adaları\nMakedoniya\nMali\nMalta\nMyanma\nMonteneqro\nMonqolustan\nŞimali Marian adaları\nMozambik\nMavritaniya\nMontserrat\nMavriki\nMalavi\nMalayziya\nMayotta\nNamibiya\nYeni Kaledoniya\nNiger\nNorfolk adası\nNigeriya\nNikaraqua\nNiue\nNiderland\nNorveç\nNepal\nNauru\nYeni Zelandiya\nOman\nPakistan\nPanama\nPitkern adaları\nPeru\nFilippin\nPalau\nPapua-Yeni Qvineya\nPolşa\nPuerto-Riko\nKXDR\nPortuqaliya\nParaqvay\nFələstin Dövləti\nFransa Polineziyası\nQətər\nRuminiya\nRusiya\nRuanda\nSəudiyyə Ərəbistanı\nSudan\nSeneqal\nSinqapur\nMüqəddəs Yelena, Askenson və Tristan-da-Kunya adaları\nŞpisbergen və Yan-Mayen\nSolomon adaları\nSyerra-Leone\nSalvador\nSan-Marino\nSomali\nSen-Pyer və Mikelon\nSerbiya\nCənubi Sudan\nSan-Tome və Prinsipi\nSurinam\nSlovakiya\nSloeniya\nİsveç\nSvazilend\nSeyşel adaları\nSuriya\nTörks və Kaykos adaları\nÇad\nToqo\nTailand\nTacikistan\nTokelau\nTürkmənistan\nŞərqi Timor\nTonqa\nTrinidad və Tobaqo\nTunis\nTürkiyə\nTuvalu\nÇin Respublikası\nTanzaniya\nUqanda\nUkrayna\nUruqvay\nABŞ\nÖzbəkistan\nVatikan\nSent-Vinsent və Qrenadin\nVenesuela\nBritaniya Virgin adaları\nAmerika Virgin adaları\nVyetnam\nVanuatu\nUollis və Futuna\nSamoa\nYəmən\nCAR\nZambiya\nZimbabve',
insert_after='act_type',
depends_on='eval:doc.purchase_type && doc.act_type == "Import"'
)
],
"Purchase Invoice": [
dict(
fieldname='is_taxes_doc',
label='Is taxes document',
fieldtype='Check',
insert_after='is_paid',
hidden=0
),
# Purchase type fields
dict(
fieldname='purchase_type',
label='Purchase Type',
fieldtype='Check',
insert_after='is_taxes_doc',
default=0
),
dict(
fieldname='act_type',
label='Act Type',
fieldtype='Select',
options='\nPurchase Act\nEQF\nImport',
insert_after='purchase_type',
depends_on='eval:doc.purchase_type'
),
dict(
fieldname='agricultural_country',
label='Country',
fieldtype='Select',
options='\nAruba\nƏfqanıstan\nAnqola\nAngilya\nAlbaniya\nAndorra\nNiderland Antil adaları\nBƏƏ\nArgentina\nErmənistan\nAmerika Samoası\nAntiqua və Barbuda\nAvstraliya\nAvstriya\nBurundi\nBelçika\nBenin\nBurkina-Faso\nBanqladeş\nBolqarıstan\nBəhreyn\nBaham adaları\nBosniya və Herseqovina\nBelarus\nBeliz\nBermud adaları\nBoliviya\nBraziliya\nBarbados\nBruney\nButan\nBotsvana\nMAR\nKanada\nKokos adaları\nİsveçrə\nÇili\nÇin\nKot-d\'ivuar\nKamerun\nKDR\nKonqo Respublikası\nKuk adaları\nKolumbiya\nKomor adaları\nKabo-Verde\nKosta-Rika\nKuba\nMilad adası\nKayman adaları\nKipr\nÇexiya\nAlmaniya\nCibuti\nDominika\nDanimarka\nDominikan Respublikası\nƏlcəzair\nEkvador\nMisir\nEritreya\nQərbi Sahara\nİspaniya\nEstoniya\nEfiopiya\nFinlandiya\nFici\nFoklend adaları\nFransa\nFarer adaları\nMikroneziya\nQabon\nBöyük Britaniya\nGürcüstan\nQana\nCəbəli-Tariq\nQvineya\nQambiya\nQvineya-Bisau\nEkvatorial Qvineya\nYunanıstan\nQrenada\nQrenlandiya\nQvatemala\nQuam\nQayana\nHonkonq\nHonduras\nXorvatiya\nHaiti\nMacarıstan\nindoneziya\nHindistan\nirlandiya\nİran\nİraq\nislandiya\nisrail\nitaliya\nYamayka\nlordaniya\nYaponiya\nQazağıstan\nKeniya\nQıızıstan\nKamboca\nKiribati\nSent-Kits və Nevis\nCənubi Koreya\nKüveyt\nLaos\nLivan\nLiberiya\nLiviya\nSent-Lüsiya\nLixtenşteyn\nŞri-Lanka\nLesoto\nLitva\nLüksemburq\nLatviya\nMakao\nMərakeş\nMonako\nMoldova\nMadaqaskar\nMaldiv adaları\nMeksika\nMarşall adaları\nMakedoniya\nMali\nMalta\nMyanma\nMonteneqro\nMonqolustan\nŞimali Marian adaları\nMozambik\nMavritaniya\nMontserrat\nMavriki\nMalavi\nMalayziya\nMayotta\nNamibiya\nYeni Kaledoniya\nNiger\nNorfolk adası\nNigeriya\nNikaraqua\nNiue\nNiderland\nNorveç\nNepal\nNauru\nYeni Zelandiya\nOman\nPakistan\nPanama\nPitkern adaları\nPeru\nFilippin\nPalau\nPapua-Yeni Qvineya\nPolşa\nPuerto-Riko\nKXDR\nPortuqaliya\nParaqvay\nFələstin Dövləti\nFransa Polineziyası\nQətər\nRuminiya\nRusiya\nRuanda\nSəudiyyə Ərəbistanı\nSudan\nSeneqal\nSinqapur\nMüqəddəs Yelena, Askenson və Tristan-da-Kunya adaları\nŞpisbergen və Yan-Mayen\nSolomon adaları\nSyerra-Leone\nSalvador\nSan-Marino\nSomali\nSen-Pyer və Mikelon\nSerbiya\nCənubi Sudan\nSan-Tome və Prinsipi\nSurinam\nSlovakiya\nSloeniya\nİsveç\nSvazilend\nSeyşel adaları\nSuriya\nTörks və Kaykos adaları\nÇad\nToqo\nTailand\nTacikistan\nTokelau\nTürkmənistan\nŞərqi Timor\nTonqa\nTrinidad və Tobaqo\nTunis\nTürkiyə\nTuvalu\nÇin Respublikası\nTanzaniya\nUqanda\nUkrayna\nUruqvay\nABŞ\nÖzbəkistan\nVatikan\nSent-Vinsent və Qrenadin\nVenesuela\nBritaniya Virgin adaları\nAmerika Virgin adaları\nVyetnam\nVanuatu\nUollis və Futuna\nSamoa\nYəmən\nCAR\nZambiya\nZimbabve',
insert_after='act_type',
depends_on='eval:doc.purchase_type && doc.act_type == "Import"'
),
# === E-TAXES PURCHASE ACT SECTION ===
dict(
fieldname='etaxes_agricultural_section',
label='E-Taxes Purchase Act',
fieldtype='Section Break',
insert_after='amended_from',
collapsible=1
),
dict(
fieldname='act_kind',
label='Act Kind',
fieldtype='Select',
options='\nAgricultural Products Act\nMetal Scrap Reception Act\nTire Products For Disposal Act\nPlastic Products For Disposal Act\nRawhide Supply\nOther Product Receipt Act',
insert_after='etaxes_agricultural_section',
description='Type of act for e-taxes'
),
dict(
fieldname='etaxes_purchase_id',
label='E-Taxes Purchase Act ID',
fieldtype='Data',
insert_after='act_kind',
read_only=1,
allow_on_submit=1,
no_copy=1
),
dict(
fieldname='etaxes_purchase_serial_number',
label='E-Taxes Serial Number',
fieldtype='Data',
insert_after='etaxes_purchase_id',
read_only=1,
in_list_view=1,
allow_on_submit=1,
no_copy=1
),
# Column break
dict(
fieldname='etaxes_purchase_column_break',
fieldtype='Column Break',
insert_after='etaxes_purchase_serial_number'
),
dict(
fieldname='etaxes_purchase_verification_code',
label='E-Taxes Verification Code',
fieldtype='Data',
insert_after='etaxes_purchase_column_break',
read_only=1,
allow_on_submit=1,
no_copy=1
),
dict(
fieldname='etaxes_purchase_send_status',
label='E-Taxes Send Status',
fieldtype='Select',
options='\nNot Sent\nCreated, not signed\nSent and Signed\nFailed',
default='Not Sent',
insert_after='etaxes_purchase_verification_code',
read_only=1,
in_list_view=1,
allow_on_submit=1,
no_copy=1
)
],
"Sales Order": [
dict(
fieldname='is_taxes_doc',
label='Is taxes document',
fieldtype='Check',
insert_after='is_subcontracted',
hidden=1
),
dict(
fieldname='taxes_doc',
label='taxes_doc',
fieldtype='Link',
options='E-Taxes Sales',
insert_after='is_taxes_doc',
hidden=1
),
# Purchase type fields
dict(
fieldname='purchase_type',
label='Purchase Type',
fieldtype='Check',
insert_after='taxes_doc',
default=0
),
dict(
fieldname='act_type',
label='Act Type',
fieldtype='Select',
options='\nPurchase Act\nEQF\nImport',
insert_after='purchase_type',
depends_on='eval:doc.purchase_type'
),
dict(
fieldname='agricultural_country',
label='Country',
fieldtype='Select',
options='\nAruba\nƏfqanıstan\nAnqola\nAngilya\nAlbaniya\nAndorra\nNiderland Antil adaları\nBƏƏ\nArgentina\nErmənistan\nAmerika Samoası\nAntiqua və Barbuda\nAvstraliya\nAvstriya\nBurundi\nBelçika\nBenin\nBurkina-Faso\nBanqladeş\nBolqarıstan\nBəhreyn\nBaham adaları\nBosniya və Herseqovina\nBelarus\nBeliz\nBermud adaları\nBoliviya\nBraziliya\nBarbados\nBruney\nButan\nBotsvana\nMAR\nKanada\nKokos adaları\nİsveçrə\nÇili\nÇin\nKot-d\'ivuar\nKamerun\nKDR\nKonqo Respublikası\nKuk adaları\nKolumbiya\nKomor adaları\nKabo-Verde\nKosta-Rika\nKuba\nMilad adası\nKayman adaları\nKipr\nÇexiya\nAlmaniya\nCibuti\nDominika\nDanimarka\nDominikan Respublikası\nƏlcəzair\nEkvador\nMisir\nEritreya\nQərbi Sahara\nİspaniya\nEstoniya\nEfiopiya\nFinlandiya\nFici\nFoklend adaları\nFransa\nFarer adaları\nMikroneziya\nQabon\nBöyük Britaniya\nGürcüstan\nQana\nCəbəli-Tariq\nQvineya\nQambiya\nQvineya-Bisau\nEkvatorial Qvineya\nYunanıstan\nQrenada\nQrenlandiya\nQvatemala\nQuam\nQayana\nHonkonq\nHonduras\nXorvatiya\nHaiti\nMacarıstan\nindoneziya\nHindistan\nirlandiya\nİran\nİraq\nislandiya\nisrail\nitaliya\nYamayka\nlordaniya\nYaponiya\nQazağıstan\nKeniya\nQıızıstan\nKamboca\nKiribati\nSent-Kits və Nevis\nCənubi Koreya\nKüveyt\nLaos\nLivan\nLiberiya\nLiviya\nSent-Lüsiya\nLixtenşteyn\nŞri-Lanka\nLesoto\nLitva\nLüksemburq\nLatviya\nMakao\nMərakeş\nMonako\nMoldova\nMadaqaskar\nMaldiv adaları\nMeksika\nMarşall adaları\nMakedoniya\nMali\nMalta\nMyanma\nMonteneqro\nMonqolustan\nŞimali Marian adaları\nMozambik\nMavritaniya\nMontserrat\nMavriki\nMalavi\nMalayziya\nMayotta\nNamibiya\nYeni Kaledoniya\nNiger\nNorfolk adası\nNigeriya\nNikaraqua\nNiue\nNiderland\nNorveç\nNepal\nNauru\nYeni Zelandiya\nOman\nPakistan\nPanama\nPitkern adaları\nPeru\nFilippin\nPalau\nPapua-Yeni Qvineya\nPolşa\nPuerto-Riko\nKXDR\nPortuqaliya\nParaqvay\nFələstin Dövləti\nFransa Polineziyası\nQətər\nRuminiya\nRusiya\nRuanda\nSəudiyyə Ərəbistanı\nSudan\nSeneqal\nSinqapur\nMüqəddəs Yelena, Askenson və Tristan-da-Kunya adaları\nŞpisbergen və Yan-Mayen\nSolomon adaları\nSyerra-Leone\nSalvador\nSan-Marino\nSomali\nSen-Pyer və Mikelon\nSerbiya\nCənubi Sudan\nSan-Tome və Prinsipi\nSurinam\nSlovakiya\nSloeniya\nİsveç\nSvazilend\nSeyşel adaları\nSuriya\nTörks və Kaykos adaları\nÇad\nToqo\nTailand\nTacikistan\nTokelau\nTürkmənistan\nŞərqi Timor\nTonqa\nTrinidad və Tobaqo\nTunis\nTürkiyə\nTuvalu\nÇin Respublikası\nTanzaniya\nUqanda\nUkrayna\nUruqvay\nABŞ\nÖzbəkistan\nVatikan\nSent-Vinsent və Qrenadin\nVenesuela\nBritaniya Virgin adaları\nAmerika Virgin adaları\nVyetnam\nVanuatu\nUollis və Futuna\nSamoa\nYəmən\nCAR\nZambiya\nZimbabve',
insert_after='act_type',
depends_on='eval:doc.purchase_type && doc.act_type == "Import"'
)
],
"Sales Order Item": [
dict(
fieldname='vat_18_percent_with_amount',
label='VAT 18% with amount',
fieldtype='Currency',
insert_after='amount',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='vat_amount',
label='VAT Amount',
fieldtype='Currency',
insert_after='vat_18_percent_with_amount',
in_list_view=1,
columns=2,
precision=2,
read_only=1,
description='Calculated as: VAT 18% with amount - Amount'
),
dict(
fieldname='vat_0_percent_with_amount',
label='VAT 0% with amount',
fieldtype='Currency',
insert_after='vat_amount',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='amount_without_vat',
label='Amount without VAT',
fieldtype='Currency',
insert_after='vat_0_percent_with_amount',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='vat_free_amount',
label='VAT free amount',
fieldtype='Currency',
insert_after='amount_without_vat',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='tax_article',
label='Tax Article',
fieldtype='Link',
options='Tax Article',
insert_after='vat_free_amount',
in_list_view=1,
columns=2,
description='Tax Article field for VAT purposes'
)
],
"Sales Invoice": [
dict(
fieldname='is_taxes_doc',
label='Is taxes document',
fieldtype='Check',
insert_after='is_paid',
hidden=0
),
# Hidden field to fetch company main activity
dict(
fieldname='company_main_activity',
label='Company Main Activity',
fieldtype='Data',
insert_after='is_taxes_doc',
hidden=1,
fetch_from='company.main_activity',
read_only=1
),
# Organizer and Seller checkboxes
dict(
fieldname='goods_provided_instead_of_salary',
label='Goods provided instead of salary',
fieldtype='Check',
insert_after='is_debit_note'
),
dict(
fieldname='organizer',
label='Organizer',
fieldtype='Check',
insert_after='goods_provided_instead_of_salary',
depends_on='eval:doc.company_main_activity && ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].includes(doc.company_main_activity)',
allow_on_submit=1
),
dict(
fieldname='seller',
label='Seller',
fieldtype='Check',
insert_after='organizer',
depends_on='eval:doc.company_main_activity && ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].includes(doc.company_main_activity)',
allow_on_submit=1
),
# Purchase type fields
dict(
fieldname='purchase_type',
label='Purchase Type',
fieldtype='Check',
insert_after='seller',
default=0
),
dict(
fieldname='act_type',
label='Act Type',
fieldtype='Select',
options='\nPurchase Act\nEQF\nImport',
insert_after='purchase_type',
depends_on='eval:doc.purchase_type'
),
dict(
fieldname='agricultural_country',
label='Country',
fieldtype='Select',
options='\nAruba\nƏfqanıstan\nAnqola\nAngilya\nAlbaniya\nAndorra\nNiderland Antil adaları\nBƏƏ\nArgentina\nErmənistan\nAmerika Samoası\nAntiqua və Barbuda\nAvstraliya\nAvstriya\nBurundi\nBelçika\nBenin\nBurkina-Faso\nBanqladeş\nBolqarıstan\nBəhreyn\nBaham adaları\nBosniya və Herseqovina\nBelarus\nBeliz\nBermud adaları\nBoliviya\nBraziliya\nBarbados\nBruney\nButan\nBotsvana\nMAR\nKanada\nKokos adaları\nİsveçrə\nÇili\nÇin\nKot-d\'ivuar\nKamerun\nKDR\nKonqo Respublikası\nKuk adaları\nKolumbiya\nKomor adaları\nKabo-Verde\nKosta-Rika\nKuba\nMilad adası\nKayman adaları\nKipr\nÇexiya\nAlmaniya\nCibuti\nDominika\nDanimarka\nDominikan Respublikası\nƏlcəzair\nEkvador\nMisir\nEritreya\nQərbi Sahara\nİspaniya\nEstoniya\nEfiopiya\nFinlandiya\nFici\nFoklend adaları\nFransa\nFarer adaları\nMikroneziya\nQabon\nBöyük Britaniya\nGürcüstan\nQana\nCəbəli-Tariq\nQvineya\nQambiya\nQvineya-Bisau\nEkvatorial Qvineya\nYunanıstan\nQrenada\nQrenlandiya\nQvatemala\nQuam\nQayana\nHonkonq\nHonduras\nXorvatiya\nHaiti\nMacarıstan\nindoneziya\nHindistan\nirlandiya\nİran\nİraq\nislandiya\nisrail\nitaliya\nYamayka\nlordaniya\nYaponiya\nQazağıstan\nKeniya\nQıızıstan\nKamboca\nKiribati\nSent-Kits və Nevis\nCənubi Koreya\nKüveyt\nLaos\nLivan\nLiberiya\nLiviya\nSent-Lüsiya\nLixtenşteyn\nŞri-Lanka\nLesoto\nLitva\nLüksemburq\nLatviya\nMakao\nMərakeş\nMonako\nMoldova\nMadaqaskar\nMaldiv adaları\nMeksika\nMarşall adaları\nMakedoniya\nMali\nMalta\nMyanma\nMonteneqro\nMonqolustan\nŞimali Marian adaları\nMozambik\nMavritaniya\nMontserrat\nMavriki\nMalavi\nMalayziya\nMayotta\nNamibiya\nYeni Kaledoniya\nNiger\nNorfolk adası\nNigeriya\nNikaraqua\nNiue\nNiderland\nNorveç\nNepal\nNauru\nYeni Zelandiya\nOman\nPakistan\nPanama\nPitkern adaları\nPeru\nFilippin\nPalau\nPapua-Yeni Qvineya\nPolşa\nPuerto-Riko\nKXDR\nPortuqaliya\nParaqvay\nFələstin Dövləti\nFransa Polineziyası\nQətər\nRuminiya\nRusiya\nRuanda\nSəudiyyə Ərəbistanı\nSudan\nSeneqal\nSinqapur\nMüqəddəs Yelena, Askenson və Tristan-da-Kunya adaları\nŞpisbergen və Yan-Mayen\nSolomon adaları\nSyerra-Leone\nSalvador\nSan-Marino\nSomali\nSen-Pyer və Mikelon\nSerbiya\nCənubi Sudan\nSan-Tome və Prinsipi\nSurinam\nSlovakiya\nSloeniya\nİsveç\nSvazilend\nSeyşel adaları\nSuriya\nTörks və Kaykos adaları\nÇad\nToqo\nTailand\nTacikistan\nTokelau\nTürkmənistan\nŞərqi Timor\nTonqa\nTrinidad və Tobaqo\nTunis\nTürkiyə\nTuvalu\nÇin Respublikası\nTanzaniya\nUqanda\nUkrayna\nUruqvay\nABŞ\nÖzbəkistan\nVatikan\nSent-Vinsent və Qrenadin\nVenesuela\nBritaniya Virgin adaları\nAmerika Virgin adaları\nVyetnam\nVanuatu\nUollis və Futuna\nSamoa\nYəmən\nCAR\nZambiya\nZimbabve',
insert_after='act_type',
depends_on='eval:doc.purchase_type && doc.act_type == "Import"'
),
# New comments section after time_sheet_list
dict(
fieldname='comments_section',
label='Comments',
fieldtype='Section Break',
insert_after='time_sheet_list',
collapsible=1
),
# Comment field
dict(
fieldname='comment',
label='Comment',
fieldtype='Small Text',
insert_after='comments_section'
),
# Column break for grouping
dict(
fieldname='comments_column_break',
fieldtype='Column Break',
insert_after='comment'
),
# Additional comment field
dict(
fieldname='additional_comment',
label='Additional Comment',
fieldtype='Small Text',
insert_after='comments_column_break'
),
# === E-TAXES INTEGRATION SECTION ===
dict(
fieldname='etaxes_section',
label='E-Taxes Integration',
fieldtype='Section Break',
insert_after='additional_comment',
collapsible=1
),
# Temporary field for user input
dict(
fieldname='customer_object_name',
label='Customer Object Name',
fieldtype='Data',
insert_after='etaxes_section',
description='Selected customer object from E-Taxes (auto-filled)',
read_only=1,
allow_on_submit=1,
no_copy=1
),
# Column break
dict(
fieldname='etaxes_column_break',
fieldtype='Column Break',
insert_after='customer_object_name'
),
# E-Taxes tracking fields (read-only after creation)
dict(
fieldname='etaxes_invoice_id',
label='E-Taxes Invoice ID',
fieldtype='Data',
insert_after='etaxes_column_break',
read_only=1,
in_list_view=1,
allow_on_submit=1,
no_copy=1
),
dict(
fieldname='etaxes_serial_number',
label='E-Taxes Serial Number',
fieldtype='Data',
insert_after='etaxes_invoice_id',
read_only=1,
in_list_view=1,
allow_on_submit=1,
no_copy=1
),
dict(
fieldname='etaxes_verification_code',
label='E-Taxes Verification Code',
fieldtype='Data',
insert_after='etaxes_serial_number',
read_only=1,
allow_on_submit=1,
no_copy=1
),
dict(
fieldname='etaxes_send_status',
label='E-Taxes Status',
fieldtype='Select',
options='\nNot Sent\nSent and Signed\nCreated, not signed',
default='Not Sent',
insert_after='etaxes_verification_code',
read_only=1,
in_list_view=1,
allow_on_submit=1,
no_copy=1
)
],
"Sales Invoice Item": [
dict(
fieldname='vat_18_percent_with_amount',
label='VAT 18% with amount',
fieldtype='Currency',
insert_after='amount',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='vat_amount',
label='VAT Amount',
fieldtype='Currency',
insert_after='vat_18_percent_with_amount',
in_list_view=1,
columns=2,
precision=2,
read_only=1,
description='Calculated as: VAT 18% with amount - Amount'
),
dict(
fieldname='vat_0_percent_with_amount',
label='VAT 0% with amount',
fieldtype='Currency',
insert_after='vat_amount',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='amount_without_vat',
label='Amount without VAT',
fieldtype='Currency',
insert_after='vat_0_percent_with_amount',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='vat_free_amount',
label='VAT free amount',
fieldtype='Currency',
insert_after='amount_without_vat',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='tax_article',
label='Tax Article',
fieldtype='Link',
options='Tax Article',
insert_after='vat_free_amount',
in_list_view=1,
columns=2,
description='Tax Article field for VAT purposes'
)
],
"Purchase Invoice Item": [
dict(
fieldname='tax_type',
label='Tax Type',
fieldtype='Select',
options='\nTax Free\nTaxable',
insert_after='item_tax_template',
depends_on='eval:parent.purchase_type',
mandatory_depends_on='eval:parent.purchase_type',
in_list_view=1,
columns=2
),
dict(
fieldname='purchase_tax_amount',
label='Purchase Tax Amount (5%)',
fieldtype='Currency',
insert_after='tax_type',
depends_on='eval:parent.purchase_type',
read_only=1,
in_list_view=1,
columns=2,
precision=2,
description='Auto-calculated as 5% of amount when Tax Type is Taxable'
)
],
"Sales Taxes and Charges": [
dict(
fieldname='tax_free_amount',
label='Tax Free Amount',
fieldtype='Currency',
insert_after='tax_amount',
in_list_view=1,
columns=2
)
],
"Item": [
# Product Group Code field in Details tab
dict(
fieldname='product_group_code',
label='Product Group Code',
fieldtype='Link',
options='E-Taxes Item Group',
insert_after='item_name',
in_list_view=0,
in_standard_filter=0
),
dict(
fieldname='eqm_code',
label='EQM Code',
fieldtype='Link',
options='EQM Codes',
insert_after='product_group_code',
in_list_view=0,
in_standard_filter=0
),
# Product Category field
dict(
fieldname='product_category',
label='Product Category',
fieldtype='Link',
options='Item Category',
insert_after='product_group_code',
in_list_view=1,
in_standard_filter=1
)
],
"Item Group": [
dict(
fieldname='is_standard',
label='Is Standard',
fieldtype='Check',
insert_after='is_group',
hidden=1,
read_only=1
)
],
"Journal Entry": [
dict(
fieldname='expense_income_type',
label='Expense/Income',
fieldtype='Select',
options='\nIncome\nExpense',
insert_after='voucher_type',
in_list_view=1,
columns=2,
no_copy=1,
allow_on_submit=1
),
dict(
fieldname='customer_name_etaxes',
fieldtype='Data',
insert_after='expense_income_type',
hidden=1,
read_only=1,
no_copy=1,
translatable=0
),
dict(
fieldname='etaxes_document_type',
label='E-Taxes Document Type',
fieldtype='Select',
options='\nədv',
insert_after='customer_name_etaxes',
in_list_view=1,
columns=2,
read_only=1,
no_copy=1
),
dict(
fieldname='loaded_from_etaxes',
label='Loaded from E-Taxes',
fieldtype='Check',
insert_after='etaxes_document_type',
default='0',
hidden=1,
read_only=1,
no_copy=1
)
],
"Bank": [
dict(
fieldname='bank_code',
label='Bank Code',
fieldtype='Data',
insert_after='swift_number'
)
],
"Bank Account": [
dict(
fieldname='payment_account',
label='Payment account (h/h)',
fieldtype='Data',
insert_after='iban'
),
dict(
fieldname='correspondent_account',
label='Correspondent account (m/h)',
fieldtype='Data',
insert_after='payment_account'
),
dict(
fieldname='bank_integration_type',
label='Bank Integration Type',
fieldtype='Select',
options='\nBank Statement Importer\nKapital Bank Settings',
insert_after='correspondent_account',
hidden=1,
read_only=1,
no_copy=1,
print_hide=1
),
dict(
fieldname='bank_integration',
label='Bank Integration',
fieldtype='Dynamic Link',
options='bank_integration_type',
insert_after='bank_integration_type',
hidden=1,
read_only=1,
no_copy=1,
print_hide=1
)
],
"Employee": [
dict(
fieldname='job_applicant',
label='Job Applicant',
fieldtype='Link',
options='Job Applicant',
insert_after='employee_number'
),
dict(
fieldname='father_name',
label='Ata adı',
fieldtype='Data',
insert_after='last_name'
),
dict(
fieldname='identification_number',
label='FİN',
fieldtype='Data',
insert_after='passport_number'
),
dict(
fieldname='ssn',
label='SSN',
fieldtype='Data',
insert_after='identification_number'
),
dict(
fieldname='citizenship_country',
label='Citizenship Country',
fieldtype='Small Text',
insert_after='status'
),
dict(
fieldname='residence_permit_fin',
label='Residence Permit FIN',
fieldtype='Small Text',
insert_after='employment_type'
),
dict(
fieldname='residence_permit_not_available',
label='Not Available',
fieldtype='Check',
insert_after='residence_permit_fin'
)
],
"Employee Transfer": [
dict(
fieldname='reason',
label='Reason',
fieldtype='Data',
insert_after='amended_from'
),
dict(
fieldname='order_number',
label='Order number',
fieldtype='Data',
insert_after='reason'
)
],
"Employee Onboarding": [
dict(
fieldname='from_date_temporary',
label='A temporary employee starts working from',
fieldtype='Date',
insert_after='boarding_begins_on'
),
dict(
fieldname='to_date_temporary',
label='A temporary employee starts working to',
fieldtype='Date',
insert_after='from_date_temporary'
)
],
"Supplier": [
# === E-TAXES INDIVIDUAL INFORMATION SECTION ===
dict(
fieldname='etaxes_individual_section',
label='E-Taxes Individual Information',
fieldtype='Section Break',
insert_after='supplier_type',
collapsible=1,
depends_on='eval:doc.supplier_type == "Individual"'
),
# Fetch button from E-Taxes (left column)
dict(
fieldname='etaxes_fetch_button_html',
fieldtype='HTML',
insert_after='etaxes_individual_section',
depends_on='eval:doc.supplier_type == "Individual" && doc.fin && doc.passport_serial_number',
options='<button class="btn btn-primary btn-sm" id="fetch-from-etaxes-btn" style="margin-bottom: 15px;">' +
'<i class="fa fa-cloud-download"></i> Fetch from E-Taxes</button>' +
'<p class="text-muted small" style="margin-top: 5px; margin-bottom: 15px;">Click to auto-fill Date of Birth, First Name, Last Name, Phone Number and Supplier Name from E-Taxes</p>'
),
# Individual fields (left column)
dict(
fieldname='fin',
label='FIN',
fieldtype='Data',
insert_after='etaxes_fetch_button_html',
depends_on='eval:doc.supplier_type == "Individual"'
),
dict(
fieldname='passport_serial_number',
label='Passport Serial Number',
fieldtype='Data',
insert_after='fin',
depends_on='eval:doc.supplier_type == "Individual"'
),
dict(
fieldname='date_of_birth',
label='Date of Birth',
fieldtype='Date',
insert_after='passport_serial_number',
depends_on='eval:doc.supplier_type == "Individual"'
),
# Column break
dict(
fieldname='individual_column_break',
fieldtype='Column Break',
insert_after='date_of_birth',
depends_on='eval:doc.supplier_type == "Individual"'
),
dict(
fieldname='first_name_individual',
label='First Name',
fieldtype='Data',
insert_after='individual_column_break',
depends_on='eval:doc.supplier_type == "Individual"'
),
dict(
fieldname='last_name_individual',
label='Last Name',
fieldtype='Data',
insert_after='first_name_individual',
depends_on='eval:doc.supplier_type == "Individual"'
),
dict(
fieldname='phone_number_individual',
label='Phone Number',
fieldtype='Data',
insert_after='last_name_individual'
)
],
"Account": [
dict(
fieldname='is_account',
label='Is Account',
fieldtype='Check',
insert_after='is_group'
),
dict(
fieldname='is_sub_account',
label='Is Sub Account',
fieldtype='Check',
insert_after='is_account'
),
dict(
fieldname='description',
label='Description',
fieldtype='Text',
insert_after='balance_must_be'
)
],
"Company": [
# === MANAGEMENT INFORMATION SECTION ===
dict(
fieldname='management_section',
label='Management Information',
fieldtype='Section Break',
insert_after='default_holiday_list',
collapsible=1
),
dict(
fieldname='ceo',
label='Chief Executive Officer',
fieldtype='Link',
options='Employee',
insert_after='management_section',
read_only=1
),
dict(
fieldname='director',
label='Director',
fieldtype='Link',
options='Employee',
insert_after='ceo'
),
dict(
fieldname='city_print',
label='City (for print formats)',
fieldtype='Data',
insert_after='director'
),
# NEW: Director information from E-Taxes
dict(
fieldname='director_name_etaxes',
label='Director Name (E-Taxes)',
fieldtype='Data',
insert_after='city_print',
read_only=1
),
dict(
fieldname='director_pin',
label='Director PIN',
fieldtype='Data',
insert_after='director_name_etaxes',
read_only=1
),
# === TAX POLICY TAB ===
dict(
fieldname='tax_policy_tab',
label='Tax Policy',
fieldtype='Tab Break',
insert_after='dashboard_tab'
),
# === BUSINESS ACTIVITIES SECTION ===
dict(
fieldname='business_activities_section',
label='Business Activities',
fieldtype='Section Break',
insert_after='tax_policy_tab',
collapsible=1
),
dict(
fieldname='main_activity',
label='Main type of activity',
fieldtype='Link',
options='Main type of activity',
insert_after='business_activities_section',
read_only=1
),
dict(
fieldname='organizer',
label='Organizer',
fieldtype='Check',
insert_after='main_activity',
depends_on='eval:["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].some(id => doc.main_activity && doc.main_activity.includes(id))',
allow_on_submit=1
),
dict(
fieldname='seller',
label='Seller',
fieldtype='Check',
insert_after='organizer',
depends_on='eval:["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].some(id => doc.main_activity && doc.main_activity.includes(id))',
allow_on_submit=1
),
dict(
fieldname='additional_activity_types',
label='Additional Activity Types',
fieldtype='Table',
options='Company Additional Activity',
insert_after='main_activity',
columns=12,
read_only=1
),
# === TAX INFORMATION SECTION ===
dict(
fieldname='tax_information_section',
label='Tax Information',
fieldtype='Section Break',
insert_after='additional_activity_types',
collapsible=1
),
dict(
fieldname='taxation_system',
label='Taxation system',
fieldtype='Select',
options='18% Value-added tax\n0% Value-added tax',
insert_after='tax_information_section',
read_only=1
),
dict(
fieldname='business_classification',
label='Business Classification',
fieldtype='Link',
options='Business Classification',
insert_after='taxation_system',
read_only=1
),
dict(
fieldname='tax_authority',
label='Tax Authority',
fieldtype='Data',
insert_after='business_classification',
read_only=1
),
dict(
fieldname='state_registration_authority',
label='State Registration Authority',
fieldtype='Data',
insert_after='tax_authority',
read_only=1
),
dict(
fieldname='property_type',
label='Property Type',
fieldtype='Data',
insert_after='state_registration_authority',
read_only=1
),
dict(
fieldname='taxpayer_activity_group',
label='Taxpayer Activity Group',
fieldtype='Data',
insert_after='property_type',
read_only=1
),
# NEW: Additional tax information fields
dict(
fieldname='legal_form_code',
label='Legal Form Code',
fieldtype='Data',
insert_after='taxpayer_activity_group',
read_only=1
),
dict(
fieldname='special_tax_regime',
label='Special Tax Regime',
fieldtype='Check',
insert_after='legal_form_code',
read_only=1
),
dict(
fieldname='tin_type',
label='TIN Type',
fieldtype='Data',
insert_after='special_tax_regime',
read_only=1
),
dict(
fieldname='is_risky_taxpayer',
label='Is Risky Taxpayer',
fieldtype='Check',
insert_after='tin_type',
read_only=1
),
dict(
fieldname='is_sport_betting_operator',
label='Sport Betting Operator',
fieldtype='Check',
insert_after='is_risky_taxpayer',
read_only=1
),
dict(
fieldname='has_active_production_object',
label='Has Active Production Object',
fieldtype='Check',
insert_after='is_sport_betting_operator',
read_only=1
),
dict(
fieldname='tax_system_type',
label='Tax System Type',
fieldtype='Data',
insert_after='has_active_production_object',
read_only=1
),
dict(
fieldname='is_taxpayer_in_cancellation_process',
label='Is Taxpayer in Cancellation Process',
fieldtype='Check',
insert_after='tax_system_type',
read_only=1
),
dict(
fieldname='is_chief_of_any_legal_entity',
label='Is Chief of Any Legal Entity',
fieldtype='Check',
insert_after='is_taxpayer_in_cancellation_process',
read_only=1
),
# === VAT INFORMATION SECTION ===
dict(
fieldname='vat_information_section',
label='VAT Information',
fieldtype='Section Break',
insert_after='is_chief_of_any_legal_entity',
collapsible=1
),
dict(
fieldname='vat_registration_date',
label='VAT registration date',
fieldtype='Date',
insert_after='vat_information_section',
read_only=1
),
dict(
fieldname='vat_certificate_number',
label='VAT certificate number',
fieldtype='Data',
insert_after='vat_registration_date',
read_only=1
),
dict(
fieldname='vat_certificate_date',
label='VAT certificate date',
fieldtype='Date',
insert_after='vat_certificate_number',
read_only=1
),
# === ORGANIZATIONAL STRUCTURE SECTION ===
dict(
fieldname='organizational_structure_section',
label='Organizational Structure',
fieldtype='Section Break',
insert_after='vat_certificate_date',
collapsible=1
),
dict(
fieldname='parent_organization',
label='Parent Organization',
fieldtype='Data',
insert_after='organizational_structure_section',
read_only=1,
description='Main organization from E-Taxes'
),
dict(
fieldname='parent_organization_tin',
label='Parent Organization TIN',
fieldtype='Data',
insert_after='parent_organization',
read_only=1
),
dict(
fieldname='affiliate_organizations',
label='Affiliate Organizations',
fieldtype='Table',
options='Company Affiliate Organization',
insert_after='parent_organization_tin',
columns=12,
read_only=1
),
dict(
fieldname='employee_count',
label='Employee Count',
fieldtype='Int',
insert_after='affiliate_organizations',
read_only=1
),
# === REGISTRATION INFORMATION SECTION ===
dict(
fieldname='registration_information_section',
label='Registration Information',
fieldtype='Section Break',
insert_after='employee_count',
collapsible=1
),
dict(
fieldname='state_registration_document_issued_date',
label='State Registration Document Issued Date',
fieldtype='Date',
insert_after='registration_information_section',
read_only=1
),
dict(
fieldname='suspension_start_date',
label='Suspension Start Date',
fieldtype='Date',
insert_after='state_registration_document_issued_date',
read_only=1
),
dict(
fieldname='suspension_end_date',
label='Suspension End Date',
fieldtype='Date',
insert_after='suspension_start_date',
read_only=1
),
dict(
fieldname='liquidation_date',
label='Liquidation Date',
fieldtype='Date',
insert_after='suspension_end_date',
read_only=1
),
# === ADDRESS INFORMATION SECTION ===
dict(
fieldname='address_information_section',
label='Address Information',
fieldtype='Section Break',
insert_after='liquidation_date',
collapsible=1
),
dict(
fieldname='legal_address_full',
label='Legal Address (Full)',
fieldtype='Data',
insert_after='address_information_section',
read_only=1
),
dict(
fieldname='actual_address_full',
label='Actual Address (Full)',
fieldtype='Data',
insert_after='legal_address_full',
read_only=1
),
dict(
fieldname='address_for_mail',
label='Address for Mail',
fieldtype='Select',
options='legal\nactual',
insert_after='actual_address_full',
read_only=1
),
dict(
fieldname='legal_address_postcode',
label='Legal Address Postcode',
fieldtype='Data',
insert_after='address_for_mail',
read_only=1
),
dict(
fieldname='legal_address_region',
label='Legal Address Region',
fieldtype='Data',
insert_after='legal_address_postcode',
read_only=1
),
dict(
fieldname='legal_address_locality',
label='Legal Address Locality',
fieldtype='Data',
insert_after='legal_address_region',
read_only=1
),
dict(
fieldname='legal_address_street',
label='Legal Address Street',
fieldtype='Data',
insert_after='legal_address_locality',
read_only=1
),
dict(
fieldname='legal_address_house',
label='Legal Address House Number',
fieldtype='Data',
insert_after='legal_address_street',
read_only=1
),
dict(
fieldname='legal_address_room',
label='Legal Address Room Number',
fieldtype='Data',
insert_after='legal_address_house',
read_only=1
),
# === CONTACT INFORMATION SECTION ===
dict(
fieldname='contact_information_section',
label='Contact Information (E-Taxes)',
fieldtype='Section Break',
insert_after='legal_address_room',
collapsible=1
),
dict(
fieldname='landline_phone',
label='Landline Phone',
fieldtype='Data',
insert_after='contact_information_section',
read_only=1
),
dict(
fieldname='mobile_phone',
label='Mobile Phone',
fieldtype='Data',
insert_after='landline_phone',
read_only=1
),
# === TAX SYSTEMS SECTION ===
dict(
fieldname='tax_systems_section',
label='Tax Systems',
fieldtype='Section Break',
insert_after='mobile_phone',
collapsible=1
),
dict(
fieldname='tax_systems_list',
label='Tax Systems List',
fieldtype='Small Text',
insert_after='tax_systems_section',
read_only=1,
description='List of tax systems applied'
),
# === EMPLOYER INFORMATION SECTION ===
dict(
fieldname='employer_information_section',
label='Employer Information',
fieldtype='Section Break',
insert_after='tax_systems_list',
),
dict(
fieldname='employer_name',
label='Employer Name',
fieldtype='Data',
insert_after='employer_information_section',
read_only=1
),
dict(
fieldname='employer_position',
label='Employer Position',
fieldtype='Data',
insert_after='employer_name',
read_only=1
),
dict(
fieldname='mdssüzrə',
label='MDSS üzrə',
fieldtype='Select',
options='\n0112 Muzdla işləyənləri işə götürən sığorta ədənləri\n0200 Müəlliflik qonorarı ödəyən hüquqi və fiziki şəxslər\n0300 Vəkilləri kollegiyasının üzvləri, sərbəst auditorlar, sərbəst mühəsibləri, sərbəst mediatorlar\n0500 Mülki hüquqi xarakterli müqavilələrlə işləyənlər',
insert_after='employer_position',
allow_on_submit=1,
),
dict(
fieldname='icbaritibbisığortaüzrə',
label='İcbari tibbi sığorta üzrə',
fieldtype='Select',
options='\n2100 Muzdla işləyənləri işə götürən sığorta ədənləri (İcbari tibbi sığorta haqq üzrə)\n2202 Vəkilləri kollegiyasının üzvləri, sərbəst auditorlar, sərbəst mühəsibləri, sərbəst mediatorlar\n2300 Mülki hüquqi müqavilələrlər əsasında işləri (xidmətləri) yerində yetirən fiziki şəxslər üzrə (fərdi sahibkarlar istisna olmaqla)',
insert_after='mdssüzrə',
allow_on_submit=1,
),
dict(
fieldname='işsizlikdənsığortaüzrə',
label='İşsizlikdən sığorta üzrə',
fieldtype='Select',
options='\n1100 Muzdla işləyənləri işə götürən sığorta ədənləri',
insert_after='icbaritibbisığortaüzrə',
allow_on_submit=1,
),
# === TAX POLICY & ACCOUNTING METHOD ===
dict(
fieldname='tax_policy',
label='Tax Policy',
fieldtype='Select',
options='Standard\nSimplified\nMicro\nSelf-employed',
insert_after='tax_system_type',
description='Tax regime for Tax Inspector audit calculations'
),
dict(
fieldname='accounting_method',
label='Uçot metodu (Accounting Method)',
fieldtype='Select',
options='\nKassa metodu\nHesablama metodu',
insert_after='tax_policy',
description='Kassa metodu — gəlir və xərclər yalnız ödəniş zamanı tanınır. ƏDV öhdəliyi yalnız ödəniş alındıqda yaranır.\nHesablama metodu — gəlir və xərclər faktura zamanı tanınır.'
),
# === E-TAXES REFERENCE DATA (invoice_az) ===
# Anchored at the very end of the Tax Policy tab so these sections
# (and Tax Closing Wizards below them) appear after all other Tax
# Policy content instead of splitting tax_information_section.
dict(
fieldname='objects_list_section',
label='E-Taxes Objects',
fieldtype='Section Break',
insert_after='işsizlikdənsığortaüzrə',
collapsible=1,
),
dict(
fieldname='objects_list_html',
fieldtype='HTML',
options='<div id="objects-list-container" class="etaxes-ref-list">Loading...</div>',
insert_after='objects_list_section',
),
dict(
fieldname='cash_registers_list_section',
label='Cash Registers',
fieldtype='Section Break',
insert_after='objects_list_html',
collapsible=1,
),
dict(
fieldname='cash_registers_list_html',
fieldtype='HTML',
options='<div id="cash-registers-list-container" class="etaxes-ref-list">Loading...</div>',
insert_after='cash_registers_list_section',
),
dict(
fieldname='pos_terminals_list_section',
label='POS Terminals',
fieldtype='Section Break',
insert_after='cash_registers_list_html',
collapsible=1,
),
dict(
fieldname='pos_terminals_list_html',
fieldtype='HTML',
options='<div id="pos-terminals-list-container" class="etaxes-ref-list">Loading...</div>',
insert_after='pos_terminals_list_section',
),
dict(
fieldname='bank_accounts_list_section',
label='Bank Accounts',
fieldtype='Section Break',
insert_after='pos_terminals_list_html',
collapsible=1,
),
dict(
fieldname='bank_accounts_list_html',
fieldtype='HTML',
options='<div id="bank-accounts-list-container" class="etaxes-ref-list">Loading...</div>',
insert_after='bank_accounts_list_section',
),
dict(
fieldname='presented_certificates_list_section',
label='Presented Certificates',
fieldtype='Section Break',
insert_after='bank_accounts_list_html',
collapsible=1,
),
dict(
fieldname='presented_certificates_list_html',
fieldtype='HTML',
options='<div id="presented-certificates-list-container" class="etaxes-ref-list">Loading...</div>',
insert_after='presented_certificates_list_section',
),
# === TAX CLOSING WIZARDS ===
dict(
fieldname='tax_wizards_section',
label='Tax Closing Wizards',
fieldtype='Section Break',
insert_after='presented_certificates_list_html',
),
dict(
fieldname='tax_wizards_html',
label='Wizards',
fieldtype='HTML',
insert_after='tax_wizards_section'
),
],
# =====================================================
# EMPLOYEE - AMAS DATA INTEGRATION
# =====================================================
"Attendance": [
dict(
fieldname='absent_reason',
label='Absence Reason',
fieldtype='Select',
insert_after='leave_type',
depends_on='eval:doc.status == "Absent"',
options='\nİşçinin ödənişli əsas məzuniyyətdən və müvafiq hallarda əlavə məzuniyyətlərindən istifadə etməsi\nİşçinin sosial məzuniyyətlərindən istifadə etməsi\nİşçinin ödənişsiz məzuniyyətlərindən istifadə etməsi\nİşçinin təhsil məzuniyyətlərindən istifadə etməsi\nİşçinin xəstələnməsi\nƏmək müqaviləsinə xitam verilməsi\nNövbəli iş vaxtı\nAyın ortası işə qəbul olma\nQeyd olunan ay üzrə işəgötürənlə əmək müqaviləsi bağlanılmamışdır.\nİşçinin işəmuzd işləməsi\nİşçinin qanunvericiliklə nəzərdə tutulmuş hallarda və qaydada dövlət və ictimai vəzifələrin yerinə yetirilməsinə cəlb edildiyi müddət\nİşçi barəsində məhkəmə hökmünün qanuni qüvvəyə minməsinədək müəyyən müddətə azadlıqdan məhrum edilməsi',
reqd=0
),
],
"Salary Slip": [
dict(
fieldname='custom_tax_free_tab',
label='Tax Free',
fieldtype='Tab Break',
# Effective last field in meta is `leave_details` inside the
# Leaves tab (hrms reorders tabs via insert_after — raw JSON
# position is misleading). Anchoring here puts Tax Free at the
# very end of the document.
insert_after='leave_details'
),
# Real .frappe-control inside Tax Free so the tab isn't auto-hidden.
# Frappe (tab.js / layout.js refresh_sections) marks any section with
# zero frappe-controls as 'empty-section', and then hides any tab
# whose sections are all empty. A Section Break alone is not enough —
# an actual field element is required. HTML field is the cheapest
# no-UI-impact option that counts as a real control.
dict(
fieldname='custom_tax_free_placeholder',
fieldtype='HTML',
options='',
insert_after='custom_tax_free_tab'
),
# === Subtab 1: Vergi ===
dict(
fieldname='custom_vergi_tab',
label='Vergi',
fieldtype='Tab Break',
insert_after='custom_tax_free_placeholder'
),
dict(
fieldname='custom_vergi_indicator',
label='Göstəricilər',
fieldtype='Link',
options='Tax Free Indicator',
link_filters='[["Tax Free Indicator","parent_tax_free_indicator","=","Vergi tutulmayan gəlirlər"],["Tax Free Indicator","is_group","=",0]]',
insert_after='custom_vergi_tab'
),
dict(
fieldname='custom_vergi_column_break',
fieldtype='Column Break',
insert_after='custom_vergi_indicator'
),
dict(
fieldname='custom_vergi_amount',
label='Amount',
fieldtype='Currency',
insert_after='custom_vergi_column_break'
),
# === Subtab 2: MDSS ===
dict(
fieldname='custom_mdss_tab',
label='MDSS',
fieldtype='Tab Break',
insert_after='custom_vergi_amount'
),
dict(
fieldname='custom_mdss_indicator',
label='Göstəricilər',
fieldtype='Link',
options='Tax Free Indicator',
link_filters='[["Tax Free Indicator","parent_tax_free_indicator","=","MDSS cəlb olunmayan gəlirlər"],["Tax Free Indicator","is_group","=",0]]',
insert_after='custom_mdss_tab'
),
dict(
fieldname='custom_mdss_column_break',
fieldtype='Column Break',
insert_after='custom_mdss_indicator'
),
dict(
fieldname='custom_mdss_amount',
label='Amount',
fieldtype='Currency',
insert_after='custom_mdss_column_break'
),
# === Subtab 3: İşsizlik sığorta ===
dict(
fieldname='custom_unemployment_insurance_tab',
label='İşsizlik sığorta',
fieldtype='Tab Break',
insert_after='custom_mdss_amount'
),
dict(
fieldname='custom_unemployment_insurance_indicator',
label='Göstəricilər',
fieldtype='Link',
options='Tax Free Indicator',
link_filters='[["Tax Free Indicator","parent_tax_free_indicator","=","İşsizlikdən sığorta haqqına cəlb olunmayan gəlirlər"],["Tax Free Indicator","is_group","=",0]]',
insert_after='custom_unemployment_insurance_tab'
),
dict(
fieldname='custom_unemployment_insurance_column_break',
fieldtype='Column Break',
insert_after='custom_unemployment_insurance_indicator'
),
dict(
fieldname='custom_unemployment_insurance_amount',
label='Amount',
fieldtype='Currency',
insert_after='custom_unemployment_insurance_column_break'
),
# === Subtab 4: İcbari Tibbi Sığorta ===
dict(
fieldname='custom_medical_insurance_tab',
label='İcbari Tibbi Sığorta',
fieldtype='Tab Break',
insert_after='custom_unemployment_insurance_amount'
),
dict(
fieldname='custom_medical_insurance_indicator',
label='Göstəricilər',
fieldtype='Link',
options='Tax Free Indicator',
link_filters='[["Tax Free Indicator","parent_tax_free_indicator","=","İcbari tibbi sığorta haqqına cəlb olunmayan gəlirlər"],["Tax Free Indicator","is_group","=",0]]',
insert_after='custom_medical_insurance_tab'
),
dict(
fieldname='custom_medical_insurance_column_break',
fieldtype='Column Break',
insert_after='custom_medical_insurance_indicator'
),
dict(
fieldname='custom_medical_insurance_amount',
label='Amount',
fieldtype='Currency',
insert_after='custom_medical_insurance_column_break'
),
],
"Item": [
# Default expense account used by Landed Cost Voucher when this
# service item is added to the Landed Cost Taxes and Charges table.
# Hidden for stock items because LCV taxes are services only.
dict(
fieldname='default_landed_cost_account',
label='Default Landed Cost Account',
fieldtype='Link',
options='Account',
insert_after='is_stock_item',
depends_on='eval:doc.is_stock_item==0',
description='Used as the Expense Account when this service item is selected in a Landed Cost Voucher.',
),
],
"Landed Cost Voucher": [
# In-form button placed right above the Landed Cost (taxes) table,
# mirroring the standard `get_items_from_purchase_receipts` button
# that sits above the items table.
dict(
fieldname='get_items_from_vendor_invoices',
label='Get Items from Vendor Invoices',
fieldtype='Button',
insert_after='sec_break1',
),
],
"Landed Cost Taxes and Charges": [
# Replaces the standard `description` field as the user-facing
# selector. Standard `description` is hidden via Property Setter
# and auto-filled from item_name on the client.
dict(
fieldname='item',
label='Item',
fieldtype='Link',
options='Item',
insert_after='description',
in_list_view=1,
columns=2,
reqd=1,
),
],
}
# Rename: remove old Cyrillic-prefixed field if it still exists
_cyrillic_field = "Bank Account-\u0441orrespondent_account"
if frappe.db.exists("Custom Field", _cyrillic_field):
try:
frappe.delete_doc("Custom Field", _cyrillic_field)
frappe.db.commit()
print("Deleted legacy Cyrillic field: сorrespondent_account")
except Exception as e:
print(f"Failed to delete legacy Cyrillic field: {e}")
# Filter out malformed entries (no fieldname) before passing to Frappe
sanitized = {}
for doctype, fields in custom_fields.items():
clean = []
for field in fields:
if 'fieldname' not in field:
print(f"Skipping field without fieldname in {doctype}: {field}")
continue
clean.append(field)
if clean:
sanitized[doctype] = clean
# Force-recreate mode for development: drop existing fields so Frappe
# creates them from scratch. Slow — only use when bulk update is not
# picking up something. Trigger via: JEY_ERP_FORCE_RECREATE_FIELDS=1 bench migrate
if os.environ.get("JEY_ERP_FORCE_RECREATE_FIELDS"):
for doctype, fields in sanitized.items():
for field in fields:
cf_name = frappe.db.get_value(
"Custom Field",
{"dt": doctype, "fieldname": field['fieldname']},
"name",
)
if cf_name:
try:
frappe.delete_doc("Custom Field", cf_name)
except Exception as e:
print(f"Failed to delete field {field['fieldname']} in {doctype}: {e}")
frappe.db.commit()
# Idempotent bulk create/update. Mirrors frappe.custom...create_custom_fields
# but saves with ignore_version=True. Why: Custom Field has track_changes=1,
# and Frappe's diff formatter (version.get_diff → format_value) crashes on
# certain int values with "expected string or bytes-like object, got 'int'".
# We don't need version history for system-managed fields anyway.
_apply_custom_fields(sanitized, ignore_validate=True)
# === Property Setters ===
# Hide and de-mandate the standard `description` field on
# Landed Cost Taxes and Charges. The new custom `item` field replaces it
# in the UI; description is auto-filled from item_name on the client so
# ERPNext server logic that reads description still works.
#
# Also pin the field_order: standard ERPNext order is
# description, col_break3, amount, expense_account, ...
# We want grid columns to read item | expense_account | amount, so we
# rearrange to: description, item, col_break3, expense_account, amount,
# account_currency, exchange_rate, base_amount,
# has_corrective_cost, has_operating_cost.
# Frappe respects field-level insert_after only for custom fields; the
# only way to reorder standard fields is a DocType-level field_order
# Property Setter (see frappe.model.meta.Meta.sort_fields).
import json as _json
_lctc_field_order = [
"description",
"item",
"col_break3",
"expense_account",
"amount",
"account_currency",
"exchange_rate",
"base_amount",
"has_corrective_cost",
"has_operating_cost",
]
_apply_property_setters([
{"doctype": "Landed Cost Taxes and Charges", "fieldname": "description",
"property": "hidden", "value": "1", "property_type": "Check"},
{"doctype": "Landed Cost Taxes and Charges", "fieldname": "description",
"property": "reqd", "value": "0", "property_type": "Check"},
{"doctype": "Landed Cost Taxes and Charges", "fieldname": "description",
"property": "in_list_view", "value": "0", "property_type": "Check"},
{"doctype": "Landed Cost Taxes and Charges", "for_doctype": True,
"property": "field_order", "value": _json.dumps(_lctc_field_order),
"property_type": "Small Text"},
])
# === Sub-tab wiring for Salary Slip via custom_subtabs ===
# The custom_subtabs app reads `js_parent_subtab` on Tab Break fields in meta.
# That property only lives on DocField (added by the app's patch). Custom Fields
# drop unknown keys on save, so we set it through Property Setters — Frappe
# applies them to Custom Field rows in meta by matching fieldname.
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
salary_slip_subtabs = {
"custom_vergi_tab": "Tax Free",
"custom_mdss_tab": "Tax Free",
"custom_unemployment_insurance_tab": "Tax Free",
"custom_medical_insurance_tab": "Tax Free",
}
frappe.clear_cache(doctype="Salary Slip")
for fn, parent_label in salary_slip_subtabs.items():
existing = frappe.db.get_value(
"Property Setter",
{
"doc_type": "Salary Slip",
"field_name": fn,
"property": "js_parent_subtab",
},
"name",
)
if existing:
try:
frappe.delete_doc("Property Setter", existing, force=True)
except Exception as e:
print(f"Failed to delete old js_parent_subtab PS for {fn}: {e}")
try:
make_property_setter(
"Salary Slip",
fn,
"js_parent_subtab",
parent_label,
"Data",
validate_fields_for_doctype=False,
)
print(f"Set js_parent_subtab={parent_label} on Salary Slip.{fn}")
except Exception as e:
print(f"Failed to set js_parent_subtab on {fn}: {e}")
frappe.clear_cache(doctype="Salary Slip")
# Коммит всех изменений
frappe.db.commit()