# ======= PYTHON API FUNCTIONS ======= import frappe import requests import json from frappe.utils import nowdate, now, cint, now_datetime, get_datetime # Список кодов игорной деятельности GAMBLING_ACTIVITY_CODES = [ '92000', '9200003', '9200004', '9200001', '9200002', '9200005' ] @frappe.whitelist() def force_update_gambling_fields(company_name, field_updates=None): """Форсированное обновление полей игорной деятельности""" import json try: # Парсим JSON если нужно if isinstance(field_updates, str): field_updates = json.loads(field_updates) if field_updates else {} # Проверяем существование компании if not frappe.db.exists("Company", company_name): return {"success": False, "message": f"Company {company_name} not found"} # Получаем текущие данные компании company = frappe.get_doc("Company", company_name) # Проверяем что это игорная деятельность if company.main_activity not in GAMBLING_ACTIVITY_CODES: return {"success": False, "message": "This operation is only allowed for gambling activities"} # Получаем текущие значения current_seller = company.seller or 0 current_organizer = company.organizer or 0 # Применяем новые значения new_seller = field_updates.get('seller', current_seller) new_organizer = field_updates.get('organizer', current_organizer) # Валидация: хотя бы одна галочка должна быть активна if not new_seller and not new_organizer: return { "success": False, "message": "At least one of Seller or Organizer must be checked for gambling activities" } # Белый список разрешенных полей для игорной деятельности allowed_gambling_fields = ['seller', 'organizer'] # Фильтруем только разрешенные поля filtered_updates = { field: value for field, value in field_updates.items() if field in allowed_gambling_fields } if not filtered_updates: return {"success": False, "message": "No valid gambling fields to update"} # Выполняем обновление через SQL set_clauses = [] values = [] for field, value in filtered_updates.items(): set_clauses.append(f"`{field}` = %s") values.append(cint(value)) if set_clauses: sql = f""" UPDATE `tabCompany` SET {', '.join(set_clauses)}, `modified` = NOW(), `modified_by` = %s WHERE `name` = %s """ values.append(frappe.session.user) values.append(company_name) frappe.db.sql(sql, tuple(values)) frappe.db.commit() # Очищаем кэш frappe.clear_cache(doctype="Company") # Логируем изменение frappe.logger().info(f"Gambling fields updated for company {company_name}: {filtered_updates}") return { "success": True, "message": f"Gambling fields updated successfully", "updated_fields": filtered_updates } except Exception as e: frappe.log_error(f"Error in force_update_gambling_fields: {str(e)}", "Gambling Fields Update Error") return { "success": False, "message": f"Error updating gambling fields: {str(e)}" } @frappe.whitelist() def validate_gambling_activity(company_name, main_activity=None): """Валидация игорной деятельности и автоматическая настройка полей""" try: if not frappe.db.exists("Company", company_name): return {"success": False, "message": f"Company {company_name} not found"} company = frappe.get_doc("Company", company_name) # Используем переданный main_activity или текущий activity_code = main_activity or company.main_activity if not activity_code: return {"success": True, "is_gambling": False, "message": "No main activity set"} is_gambling = activity_code in GAMBLING_ACTIVITY_CODES updates_needed = {} if is_gambling: # Для игорной деятельности if not company.seller and not company.organizer: # Автоматически включаем обе галочки updates_needed['seller'] = 1 updates_needed['organizer'] = 1 else: # Для негавой деятельности - выключаем галочки if company.seller or company.organizer: updates_needed['seller'] = 0 updates_needed['organizer'] = 0 # Применяем изменения если нужно if updates_needed: set_clauses = [] values = [] for field, value in updates_needed.items(): set_clauses.append(f"`{field}` = %s") values.append(cint(value)) sql = f""" UPDATE `tabCompany` SET {', '.join(set_clauses)}, `modified` = NOW(), `modified_by` = %s WHERE `name` = %s """ values.append(frappe.session.user) values.append(company_name) frappe.db.sql(sql, tuple(values)) frappe.db.commit() frappe.clear_cache(doctype="Company") return { "success": True, "is_gambling": is_gambling, "updates_applied": updates_needed, "message": f"Activity validation completed for {activity_code}" } except Exception as e: frappe.log_error(f"Error in validate_gambling_activity: {str(e)}", "Gambling Activity Validation Error") return { "success": False, "message": f"Error validating gambling activity: {str(e)}" } @frappe.whitelist() def get_company_profile(asan_login_name=None): """Получает профиль компании из E-Taxes""" record_etaxes_activity() try: if not asan_login_name: default_settings = get_default_asan_login() if not default_settings.get("found", False): return {"success": False, "message": "No Asan Login settings found."} asan_login_name = default_settings.get("name") asan_login = frappe.get_doc("Asan Login", asan_login_name) if not asan_login.main_token: return {"success": False, "message": "Main token not found. Please authenticate first."} if asan_login.auth_status != "Fully Authenticated": return {"success": False, "message": "Not fully authenticated. Please complete authentication process."} url = "https://new.e-taxes.gov.az/api/po/profile/public/v2/profile" headers = { "Accept": "application/json, text/plain, */*", "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", "Cache-Control": "no-cache", "Pragma": "no-cache", "x-authorization": f"Bearer {asan_login.main_token}" } response = requests.get(url, headers=headers, timeout=15) if response.status_code == 500: return { "error": "server_error", "message": "E-Taxes service temporarily unavailable. Please try again in a few minutes.", "status_code": 500 } if response.status_code == 401: frappe.db.set_value("Asan Login", asan_login_name, "auth_status", "Not Authenticated") frappe.db.commit() return { "error": "unauthorized", "message": "Authentication required. Please login again.", "status_code": 401 } response.raise_for_status() profile_data = response.json() frappe.logger().info(f"Company profile retrieved successfully for {asan_login_name}") return { "success": True, "data": profile_data, "message": "Company profile retrieved successfully" } except requests.exceptions.HTTPError as e: if e.response.status_code == 401: return { "error": "unauthorized", "message": "Authentication required. Please login again.", "status_code": 401 } elif e.response.status_code == 500: return { "error": "server_error", "message": "E-Taxes service temporarily unavailable. Please try again in a few minutes.", "status_code": 500 } frappe.log_error(f"HTTP error in get_company_profile: {str(e)}", "Company Profile API Error") return { "error": "http_error", "message": "An error occurred while fetching company profile" } except requests.exceptions.RequestException as e: frappe.log_error(f"Request error in get_company_profile: {str(e)}", "Company Profile API Error") return { "error": "network_error", "message": "Network error occurred. Please check your connection and try again." } except Exception as e: frappe.log_error(f"Unexpected error in get_company_profile: {str(e)}\n{frappe.get_traceback()}", "Company Profile Error") return { "error": "unknown_error", "message": "An unknown error occurred, please try again in a few minutes." } @frappe.whitelist() def get_tax_authorities(asan_login_name=None): """Получает справочник налоговых органов из E-Taxes""" record_etaxes_activity() try: if not asan_login_name: default_settings = get_default_asan_login() if not default_settings.get("found", False): return {"success": False, "message": "No Asan Login settings found."} asan_login_name = default_settings.get("name") asan_login = frappe.get_doc("Asan Login", asan_login_name) if not asan_login.main_token: return {"success": False, "message": "Main token not found. Please authenticate first."} if asan_login.auth_status != "Fully Authenticated": return {"success": False, "message": "Not fully authenticated. Please complete authentication process."} url = "https://new.e-taxes.gov.az/api/po/dictionary/public/v1/taxAuthorities" headers = { "Accept": "application/json, text/plain, */*", "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", "Cache-Control": "no-cache", "Pragma": "no-cache", "x-authorization": f"Bearer {asan_login.main_token}" } response = requests.get(url, headers=headers, timeout=15) if response.status_code == 500: return { "error": "server_error", "message": "E-Taxes service temporarily unavailable. Please try again in a few minutes.", "status_code": 500 } if response.status_code == 401: frappe.db.set_value("Asan Login", asan_login_name, "auth_status", "Not Authenticated") frappe.db.commit() return { "error": "unauthorized", "message": "Authentication required. Please login again.", "status_code": 401 } response.raise_for_status() tax_authorities_data = response.json() frappe.logger().info(f"Tax authorities dictionary retrieved successfully for {asan_login_name}") return { "success": True, "data": tax_authorities_data, "message": "Tax authorities dictionary retrieved successfully" } except requests.exceptions.HTTPError as e: if e.response.status_code == 401: return { "error": "unauthorized", "message": "Authentication required. Please login again.", "status_code": 401 } elif e.response.status_code == 500: return { "error": "server_error", "message": "E-Taxes service temporarily unavailable. Please try again in a few minutes.", "status_code": 500 } frappe.log_error(f"HTTP error in get_tax_authorities: {str(e)}", "Tax Authorities API Error") return { "error": "http_error", "message": "An error occurred while fetching tax authorities dictionary" } except requests.exceptions.RequestException as e: frappe.log_error(f"Request error in get_tax_authorities: {str(e)}", "Tax Authorities API Error") return { "error": "network_error", "message": "Network error occurred. Please check your connection and try again." } except Exception as e: frappe.log_error(f"Unexpected error in get_tax_authorities: {str(e)}\n{frappe.get_traceback()}", "Tax Authorities Error") return { "error": "unknown_error", "message": "An unknown error occurred, please try again in a few minutes." } @frappe.whitelist() def get_property_types(asan_login_name=None): """Получает справочник типов собственности из E-Taxes""" record_etaxes_activity() try: if not asan_login_name: default_settings = get_default_asan_login() if not default_settings.get("found", False): return {"success": False, "message": "No Asan Login settings found."} asan_login_name = default_settings.get("name") asan_login = frappe.get_doc("Asan Login", asan_login_name) if not asan_login.main_token: return {"success": False, "message": "Main token not found. Please authenticate first."} if asan_login.auth_status != "Fully Authenticated": return {"success": False, "message": "Not fully authenticated. Please complete authentication process."} url = "https://new.e-taxes.gov.az/api/po/profile/public/v1/dictionary/propertyTypes" headers = { "Accept": "application/json, text/plain, */*", "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", "Cache-Control": "no-cache", "Pragma": "no-cache", "x-authorization": f"Bearer {asan_login.main_token}" } response = requests.get(url, headers=headers, timeout=15) if response.status_code == 500: return { "error": "server_error", "message": "E-Taxes service temporarily unavailable. Please try again in a few minutes.", "status_code": 500 } if response.status_code == 401: frappe.db.set_value("Asan Login", asan_login_name, "auth_status", "Not Authenticated") frappe.db.commit() return { "error": "unauthorized", "message": "Authentication required. Please login again.", "status_code": 401 } response.raise_for_status() property_types_data = response.json() frappe.logger().info(f"Property types dictionary retrieved successfully for {asan_login_name}") return { "success": True, "data": property_types_data, "message": "Property types dictionary retrieved successfully" } except requests.exceptions.HTTPError as e: if e.response.status_code == 401: return { "error": "unauthorized", "message": "Authentication required. Please login again.", "status_code": 401 } elif e.response.status_code == 500: return { "error": "server_error", "message": "E-Taxes service temporarily unavailable. Please try again in a few minutes.", "status_code": 500 } frappe.log_error(f"HTTP error in get_property_types: {str(e)}", "Property Types API Error") return { "error": "http_error", "message": "An error occurred while fetching property types dictionary" } except requests.exceptions.RequestException as e: frappe.log_error(f"Request error in get_property_types: {str(e)}", "Property Types API Error") return { "error": "network_error", "message": "Network error occurred. Please check your connection and try again." } except Exception as e: frappe.log_error(f"Unexpected error in get_property_types: {str(e)}\n{frappe.get_traceback()}", "Property Types Error") return { "error": "unknown_error", "message": "An unknown error occurred, please try again in a few minutes." } @frappe.whitelist() def get_tax_authority_name_by_code(code, asan_login_name=None): """Получает название налогового органа по коду""" try: tax_authorities_result = get_tax_authorities(asan_login_name) if not tax_authorities_result.get("success"): return { "success": False, "message": tax_authorities_result.get("message", "Failed to get tax authorities dictionary") } tax_authorities = tax_authorities_result.get("data", []) if tax_authorities and isinstance(tax_authorities, list): for authority in tax_authorities: if authority.get("code") == code: name = authority.get("name", {}) if isinstance(name, dict) and name.get("az"): return { "success": True, "name": name.get("az"), "code": code } elif isinstance(name, str): return { "success": True, "name": name, "code": code } return { "success": True, "name": code, "code": code, "message": "Tax authority name not found, returning code" } except Exception as e: frappe.log_error(f"Error in get_tax_authority_name_by_code: {str(e)}", "Tax Authority Name Error") return { "success": False, "message": "An error occurred while getting tax authority name" } @frappe.whitelist() def get_property_type_name_by_code(code, asan_login_name=None): """Получает название типа собственности по коду""" try: property_types_result = get_property_types(asan_login_name) if not property_types_result.get("success"): return { "success": False, "message": property_types_result.get("message", "Failed to get property types dictionary") } property_types = property_types_result.get("data", {}) if property_types and isinstance(property_types, dict): for property_type in property_types.values(): if isinstance(property_type, dict) and property_type.get("code") == code: name = property_type.get("name", {}) if isinstance(name, dict) and name.get("az"): return { "success": True, "name": name.get("az"), "code": code } elif isinstance(name, str): return { "success": True, "name": name, "code": code } return { "success": True, "name": code, "code": code, "message": "Property type name not found, returning code" } except Exception as e: frappe.log_error(f"Error in get_property_type_name_by_code: {str(e)}", "Property Type Name Error") return { "success": False, "message": "An error occurred while getting property type name" } @frappe.whitelist() def force_update_declaration_defaults(company_name, mdss, icbari, issizlik): """Обновление полей умолчаний деклараций на компании, минуя проверку submit-статуса""" try: if not frappe.db.exists("Company", company_name): return {"success": False, "message": f"Company {company_name} not found"} frappe.db.sql(""" UPDATE `tabCompany` SET `mdssüzrə` = %s, `icbaritibbisığortaüzrə` = %s, `işsizlikdənsığortaüzrə` = %s, `modified` = NOW(), `modified_by` = %s WHERE `name` = %s """, (mdss or '', icbari or '', issizlik or '', frappe.session.user, company_name)) frappe.db.commit() return {"success": True} except Exception as e: frappe.log_error(f"Error in force_update_declaration_defaults: {str(e)}", "Declaration Defaults Error") return {"success": False, "message": str(e)} @frappe.whitelist() def force_update_single_field(company_name, field_name, field_value): """Форсированное обновление одного поля компании""" try: if not company_name or not field_name: return {"success": False, "message": "Company name and field name are required"} if not frappe.db.exists("Company", company_name): return {"success": False, "message": f"Company {company_name} not found"} allowed_fields = [ 'main_activity', 'tax_id', 'phone_no', 'email', 'director_name_etaxes', 'business_classification', 'tax_authority', 'property_type', 'vat_certificate_number' ] if field_name not in allowed_fields: return {"success": False, "message": f"Field {field_name} is not allowed for force update"} if field_name == 'main_activity' and field_value: if not frappe.db.exists("Main type of activity", field_value): return {"success": False, "message": f"Main type of activity {field_value} does not exist"} frappe.db.sql(""" UPDATE `tabCompany` SET `{field}` = %s, `modified` = NOW(), `modified_by` = %s WHERE `name` = %s """.format(field=field_name), (field_value, frappe.session.user, company_name)) frappe.db.commit() frappe.clear_cache(doctype="Company") # Если обновляли main_activity, проверяем игорную деятельность if field_name == 'main_activity': validate_gambling_activity(company_name, field_value) return { "success": True, "message": f"Field {field_name} updated successfully" } except Exception as e: frappe.log_error(f"Error in force_update_single_field: {str(e)}", "Force Update Error") return { "success": False, "message": f"Error updating field: {str(e)}" } @frappe.whitelist() def force_update_company(company_name, field_updates=None, additional_activities=None, affiliate_names=None, affiliate_tins=None): """Форсированное обновление полей компании через прямые SQL-запросы""" import json try: # Парсим JSON если нужно if isinstance(field_updates, str): field_updates = json.loads(field_updates) if field_updates else {} if isinstance(additional_activities, str): additional_activities = json.loads(additional_activities) if additional_activities else [] if isinstance(affiliate_names, str): affiliate_names = json.loads(affiliate_names) if affiliate_names else [] if isinstance(affiliate_tins, str): affiliate_tins = json.loads(affiliate_tins) if affiliate_tins else [] # Проверяем существование компании if not frappe.db.exists("Company", company_name): return {"success": False, "message": f"Company {company_name} not found"} # Обновляем основные поля через SQL if field_updates: set_clauses = [] values = [] for field, value in field_updates.items(): set_clauses.append(f"`{field}` = %s") values.append(value) if set_clauses: sql = f""" UPDATE `tabCompany` SET {', '.join(set_clauses)}, `modified` = NOW(), `modified_by` = %s WHERE `name` = %s """ values.append(frappe.session.user) values.append(company_name) frappe.db.sql(sql, tuple(values)) # Обработка additional activities activities_added = 0 if additional_activities and len(additional_activities) > 0: # ИСПРАВЛЕНО: Правильное имя таблицы table_name = "Company Additional Activity" # БЕЗ "Types" в конце! if not frappe.db.table_exists(table_name): frappe.log_error(f"Table '{table_name}' does not exist", "Table Check Error") return {"success": False, "message": f"Table '{table_name}' does not exist"} # Удаляем старые записи frappe.db.sql(f"""DELETE FROM `tab{table_name}` WHERE parent = %s""", company_name) # Добавляем новые additional activities for idx, activity_code in enumerate(additional_activities): if activity_code and activity_code.strip(): # Проверяем существование activity в справочнике activity_exists = frappe.db.exists("Main type of activity", activity_code.strip()) if activity_exists: # Генерируем уникальное имя record_name = frappe.generate_hash(length=10) frappe.db.sql(f""" INSERT INTO `tab{table_name}` (name, parent, parenttype, parentfield, idx, activity_type, docstatus, creation, modified, modified_by, owner) VALUES (%s, %s, %s, %s, %s, %s, %s, NOW(), NOW(), %s, %s) """, ( record_name, company_name, 'Company', 'additional_activity_types', # fieldname остается тем же idx + 1, activity_code.strip(), 0, frappe.session.user, frappe.session.user )) activities_added += 1 frappe.logger().info(f"Added additional activity: {activity_code} for company {company_name}") else: frappe.logger().warning(f"Activity code {activity_code} not found in Main type of activity") # Проверяем, что записи действительно добавились count = frappe.db.sql(f""" SELECT COUNT(*) FROM `tab{table_name}` WHERE parent = %s """, company_name)[0][0] frappe.logger().info(f"Total additional activities in DB after update: {count}") # Обработка affiliate organizations affiliates_added = 0 if (affiliate_names or affiliate_tins): # ИСПРАВЛЕНО: Правильное имя таблицы table_name = "Company Affiliate Organization" # В единственном числе! if frappe.db.table_exists(table_name): # Удаляем старые записи frappe.db.sql(f"""DELETE FROM `tab{table_name}` WHERE parent = %s""", company_name) # Добавляем новые affiliate organizations max_length = max(len(affiliate_names or []), len(affiliate_tins or [])) for idx in range(max_length): org_name = affiliate_names[idx] if affiliate_names and idx < len(affiliate_names) else "" org_tin = affiliate_tins[idx] if affiliate_tins and idx < len(affiliate_tins) else "" if org_name or org_tin: if not org_name and org_tin: org_name = f"Organization (TIN: {org_tin})" record_name = frappe.generate_hash(length=10) frappe.db.sql(f""" INSERT INTO `tab{table_name}` (name, parent, parenttype, parentfield, idx, organization_name, organization_tin, docstatus, creation, modified, modified_by, owner) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW(), %s, %s) """, ( record_name, company_name, 'Company', 'affiliate_organizations', # fieldname остается тем же idx + 1, org_name, org_tin or None, 0, frappe.session.user, frappe.session.user )) affiliates_added += 1 frappe.logger().info(f"Added affiliate organization: {org_name} (TIN: {org_tin}) for company {company_name}") else: frappe.logger().error(f"Table '{table_name}' does not exist") # Коммитим изменения frappe.db.commit() # Очищаем весь кэш для компании frappe.clear_cache(doctype="Company") frappe.clear_document_cache("Company", company_name) # Если обновляли main_activity, проверяем игорную деятельность if field_updates and 'main_activity' in field_updates: validate_gambling_activity(company_name, field_updates['main_activity']) # Возвращаем детальную информацию об обновлении return { "success": True, "message": "Company updated successfully", "updated_fields": len(field_updates) if field_updates else 0, "additional_activities_added": activities_added, "affiliates_added": affiliates_added } except Exception as e: frappe.log_error(f"Error in force_update_company: {str(e)}\n{frappe.get_traceback()}", "Company Update Error") frappe.db.rollback() return { "success": False, "message": f"Error updating company: {str(e)}" } # Helper functions def record_etaxes_activity(): """Placeholder for activity recording""" pass def get_default_asan_login(): """Получает настройки по умолчанию для Asan Login""" try: default_login = frappe.get_list( "Asan Login", filters={"is_default": 1}, fields=["name"] ) if default_login: asan_login = frappe.get_doc("Asan Login", default_login[0].name) return { "found": True, "name": asan_login.name, "phone": asan_login.phone, "user_id": asan_login.user_id, "auth_status": asan_login.auth_status, "bearer_token": asan_login.bearer_token, "main_token": asan_login.main_token, "selected_certificate": asan_login.selected_certificate } else: return {"found": False, "message": "No Asan Login settings found."} except Exception as e: return {"found": False, "error": str(e)} @frappe.whitelist() def check_company_structure(company_name): """Диагностическая функция для проверки структуры Company""" try: company = frappe.get_doc("Company", company_name) # Проверяем наличие полей has_additional_activities = hasattr(company, 'additional_activity_types') has_affiliate_orgs = hasattr(company, 'affiliate_organizations') # ИСПРАВЛЕНО: Правильные имена таблиц table1_exists = frappe.db.table_exists("Company Additional Activity") table2_exists = frappe.db.table_exists("Company Affiliate Organization") # Получаем текущее количество записей activities_count = 0 affiliates_count = 0 if table1_exists: activities_count = frappe.db.sql(""" SELECT COUNT(*) FROM `tabCompany Additional Activity` WHERE parent = %s """, company_name)[0][0] if table2_exists: affiliates_count = frappe.db.sql(""" SELECT COUNT(*) FROM `tabCompany Affiliate Organization` WHERE parent = %s """, company_name)[0][0] # Получаем метаданные о child tables meta = frappe.get_meta("Company") child_tables = [] for field in meta.fields: if field.fieldtype == "Table": child_tables.append({ "fieldname": field.fieldname, "options": field.options, "label": field.label }) return { "success": True, "has_additional_activities_field": has_additional_activities, "has_affiliate_orgs_field": has_affiliate_orgs, "table1_exists": table1_exists, "table2_exists": table2_exists, "activities_count_in_db": activities_count, "affiliates_count_in_db": affiliates_count, "child_tables": child_tables } except Exception as e: return { "success": False, "error": str(e) }