diff --git a/jey_erp/custom/sales_invoice.py b/jey_erp/custom/sales_invoice.py
index 3d656d9..f2ff4bd 100644
--- a/jey_erp/custom/sales_invoice.py
+++ b/jey_erp/custom/sales_invoice.py
@@ -3,6 +3,36 @@ import json
from frappe.utils import flt
+def validate_won_lost_fields(doc, method=None):
+ """
+ Ensure that exactly one of won/lost checkboxes is always selected for gambling companies
+ Automatically fixes the issue instead of throwing an error
+ """
+ if doc.doctype != "Sales Invoice":
+ return
+
+ # Check if this is a gambling company
+ allowed_activities = ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"]
+ company_main_activity = doc.get('company_main_activity')
+
+ if not company_main_activity or company_main_activity not in allowed_activities:
+ # Not a gambling company - skip validation
+ return
+
+ # Check if both fields exist
+ if not hasattr(doc, 'won') or not hasattr(doc, 'lost'):
+ return
+
+ # Для gambling компаний - автоматически исправляем ситуацию
+ if not doc.won and not doc.lost:
+ # Если обе выключены - включаем won по умолчанию
+ doc.won = 1
+ doc.lost = 0
+ elif doc.won and doc.lost:
+ # Если обе включены - оставляем только won
+ doc.lost = 0
+
+
def calculate_tax_free_amounts(doc, method=None):
"""
Calculate tax free amounts for Sales Invoice
diff --git a/jey_erp/custom_fields.py b/jey_erp/custom_fields.py
index a10c77b..6e00060 100644
--- a/jey_erp/custom_fields.py
+++ b/jey_erp/custom_fields.py
@@ -607,43 +607,26 @@ def create_custom_fields():
insert_after='act_type',
depends_on='eval:doc.purchase_type && doc.act_type == "Import"'
),
- # Won and Lost checkboxes after section_break_42 (только HTML для v15)
- dict(
- fieldname='won_lost_html',
- fieldtype='HTML',
- insert_after='section_break_42',
- depends_on='eval:doc.company_main_activity && ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].includes(doc.company_main_activity)',
- options="""
-
-
-
-
- """
- ),
- # Скрытые поля для хранения значений
+ # Won and Lost checkboxes - в одну строку
dict(
fieldname='won',
label='Won',
fieldtype='Check',
- insert_after='won_lost_html',
- hidden=1,
+ insert_after='section_break_42',
+ depends_on='eval:doc.company_main_activity && ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].includes(doc.company_main_activity)',
default=1,
- allow_on_submit=1
+ allow_on_submit=1,
+ mandatory_depends_on='eval:doc.company_main_activity && ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].includes(doc.company_main_activity)'
),
dict(
fieldname='lost',
- label='Lost',
+ label='Lost',
fieldtype='Check',
insert_after='won',
- hidden=1,
+ depends_on='eval:doc.company_main_activity && ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].includes(doc.company_main_activity)',
default=0,
- allow_on_submit=1
+ allow_on_submit=1,
+ mandatory_depends_on='eval:doc.company_main_activity && ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"].includes(doc.company_main_activity)'
),
# New comments section after time_sheet_list
dict(
diff --git a/jey_erp/hooks.py b/jey_erp/hooks.py
index e3229fd..f6b3ce0 100644
--- a/jey_erp/hooks.py
+++ b/jey_erp/hooks.py
@@ -34,8 +34,12 @@ doc_events = {
},
"Sales Invoice": {
"validate": [
+ "jey_erp.custom.sales_invoice.validate_won_lost_fields",
"jey_erp.custom.sales_invoice.calculate_tax_free_amounts",
],
+ "before_save": [
+ "jey_erp.custom.sales_invoice.validate_won_lost_fields",
+ ],
"on_update": [
"jey_erp.custom.sales_invoice.calculate_tax_free_amounts",
]
diff --git a/jey_erp/patches.txt b/jey_erp/patches.txt
index 419bef0..0e2687e 100644
--- a/jey_erp/patches.txt
+++ b/jey_erp/patches.txt
@@ -4,4 +4,6 @@
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
-jey_erp.patches.create_asset_categories_post_migration
\ No newline at end of file
+jey_erp.patches.create_asset_categories_post_migration
+jey_erp.patches.remove_old_won_lost_fields
+jey_erp.patches.remove_won_lost_breaks
\ No newline at end of file
diff --git a/jey_erp/patches/remove_old_won_lost_fields.py b/jey_erp/patches/remove_old_won_lost_fields.py
new file mode 100644
index 0000000..2c4161d
--- /dev/null
+++ b/jey_erp/patches/remove_old_won_lost_fields.py
@@ -0,0 +1,17 @@
+import frappe
+
+def execute():
+ """Remove old won_lost_html field if it exists"""
+
+ # Remove old HTML field
+ if frappe.db.exists("Custom Field", {"dt": "Sales Invoice", "fieldname": "won_lost_html"}):
+ try:
+ custom_field_name = frappe.db.get_value("Custom Field",
+ {"dt": "Sales Invoice", "fieldname": "won_lost_html"}, "name")
+ frappe.delete_doc("Custom Field", custom_field_name)
+ frappe.db.commit()
+ print("Deleted old won_lost_html field")
+ except Exception as e:
+ print(f"Failed to delete won_lost_html: {e}")
+
+ print("Cleanup completed")
diff --git a/jey_erp/patches/remove_won_lost_breaks.py b/jey_erp/patches/remove_won_lost_breaks.py
new file mode 100644
index 0000000..4531ef4
--- /dev/null
+++ b/jey_erp/patches/remove_won_lost_breaks.py
@@ -0,0 +1,22 @@
+import frappe
+
+def execute():
+ """Remove old column break and section break fields for won/lost"""
+
+ fields_to_remove = [
+ "won_lost_column_break",
+ "won_lost_section_break_end"
+ ]
+
+ for fieldname in fields_to_remove:
+ if frappe.db.exists("Custom Field", {"dt": "Sales Invoice", "fieldname": fieldname}):
+ try:
+ custom_field_name = frappe.db.get_value("Custom Field",
+ {"dt": "Sales Invoice", "fieldname": fieldname}, "name")
+ frappe.delete_doc("Custom Field", custom_field_name)
+ print(f"Deleted field: {fieldname}")
+ except Exception as e:
+ print(f"Failed to delete {fieldname}: {e}")
+
+ frappe.db.commit()
+ print("Cleanup completed")
diff --git a/jey_erp/public/js/sales_invoice_vat.js b/jey_erp/public/js/sales_invoice_vat.js
index d717797..f4b14f3 100644
--- a/jey_erp/public/js/sales_invoice_vat.js
+++ b/jey_erp/public/js/sales_invoice_vat.js
@@ -1,168 +1,152 @@
// Запускаем логику калькулятора НДС для Sales Invoice
jey_erp.vat_calculator.init('Sales Invoice');
-// Логика для чекбоксов Won/Lost и Seller/Organizer
+// Простая логика для чекбоксов Won/Lost и Seller/Organizer
frappe.ui.form.on('Sales Invoice', {
refresh: function(frm) {
- // Убедимся что одна из галочек Won/Lost всегда выбрана при загрузке формы
- if (!frm.doc.won && !frm.doc.lost) {
- frm.set_value('won', 1);
+ // Убедимся что одна из галочек Won/Lost выбрана (для gambling компаний)
+ if (is_gambling_company(frm)) {
+ if (!frm.doc.won && !frm.doc.lost) {
+ frm.set_value('won', 1);
+ }
}
-
+
// Убедимся что одна из галочек Seller/Organizer выбрана (если поля видимы)
if (should_show_seller_organizer(frm)) {
if (!frm.doc.seller && !frm.doc.organizer) {
frm.set_value('organizer', 1);
}
}
-
- // Настройка HTML чекбоксов после загрузки формы
- setTimeout(function() {
- setup_won_lost_html_checkboxes(frm);
- }, 1000);
+
+ // Применяем CSS для отображения Won и Lost в одну строку близко друг к другу
+ apply_inline_checkbox_style(frm);
},
-
- // Обработчики для Won/Lost
+
+ onload: function(frm) {
+ // Применяем стиль при загрузке формы
+ apply_inline_checkbox_style(frm);
+ },
+
+ // Обработчики для Won/Lost - гарантируем что всегда одна галочка включена
won: function(frm) {
- // Синхронизируем HTML чекбоксы при изменении скрытых полей
- update_html_checkboxes(frm);
+ if (!is_gambling_company(frm)) return;
+
if (frm.doc.won) {
- frm.set_value('lost', 0);
+ // Включили won - выключаем lost
+ if (frm.doc.lost) {
+ frm.set_value('lost', 0);
+ }
} else {
+ // Выключили won - автоматически включаем lost
frm.set_value('lost', 1);
}
},
-
+
lost: function(frm) {
- // Синхронизируем HTML чекбоксы при изменении скрытых полей
- update_html_checkboxes(frm);
+ if (!is_gambling_company(frm)) return;
+
if (frm.doc.lost) {
- frm.set_value('won', 0);
+ // Включили lost - выключаем won
+ if (frm.doc.won) {
+ frm.set_value('won', 0);
+ }
} else {
+ // Выключили lost - автоматически включаем won
frm.set_value('won', 1);
}
},
-
+
+ before_save: function(frm) {
+ // Перед сохранением убеждаемся что одна галочка включена
+ if (is_gambling_company(frm)) {
+ if (!frm.doc.won && !frm.doc.lost) {
+ frm.set_value('won', 1);
+ }
+ }
+ },
+
+ validate: function(frm) {
+ // Валидация для gambling компаний - автоматически исправляем
+ if (is_gambling_company(frm)) {
+ if (!frm.doc.won && !frm.doc.lost) {
+ // Если обе выключены, включаем won
+ frm.set_value('won', 1);
+ } else if (frm.doc.won && frm.doc.lost) {
+ // Если обе включены, оставляем только won
+ frm.set_value('lost', 0);
+ }
+ }
+ },
+
// Обработчики для Seller/Organizer
seller: function(frm) {
if (should_show_seller_organizer(frm)) {
if (frm.doc.seller) {
frm.set_value('organizer', 0);
- } else {
- // Если seller снят, обязательно ставим organizer
+ } else if (!frm.doc.organizer) {
+ // Если seller снят и organizer тоже не выбран, ставим organizer
frm.set_value('organizer', 1);
}
}
},
-
+
organizer: function(frm) {
if (should_show_seller_organizer(frm)) {
if (frm.doc.organizer) {
frm.set_value('seller', 0);
- } else {
- // Если organizer снят, обязательно ставим seller
+ } else if (!frm.doc.seller) {
+ // Если organizer снят и seller тоже не выбран, ставим seller
frm.set_value('seller', 1);
}
}
},
-
+
// Обработчик изменения company_main_activity
company_main_activity: function(frm) {
// Если поля Seller/Organizer должны быть видимы, устанавливаем значения по умолчанию
if (should_show_seller_organizer(frm)) {
if (!frm.doc.seller && !frm.doc.organizer) {
frm.set_value('organizer', 1);
- frm.set_value('seller', 0);
- }
- }
- },
-
- // При валидации формы
- validate: function(frm) {
- // Проверяем Won/Lost
- if (!frm.doc.won && !frm.doc.lost) {
- frm.set_value('won', 1);
- frm.set_value('lost', 0);
- }
-
- // Проверяем Seller/Organizer (если поля должны быть видимы)
- if (should_show_seller_organizer(frm)) {
- if (!frm.doc.seller && !frm.doc.organizer) {
- frm.set_value('organizer', 1);
- frm.set_value('seller', 0);
}
}
}
});
-// Функция проверки, должны ли быть видимы поля Seller/Organizer
-function should_show_seller_organizer(frm) {
+// Функция проверки, является ли компания gambling компанией
+function is_gambling_company(frm) {
const allowed_activities = ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"];
- return frm.doc.company_main_activity &&
+ return frm.doc.company_main_activity &&
allowed_activities.includes(frm.doc.company_main_activity);
}
-// Функция настройки HTML чекбоксов для Won/Lost
-function setup_won_lost_html_checkboxes(frm) {
- const wonCheckbox = document.querySelector('.won-checkbox');
- const lostCheckbox = document.querySelector('.lost-checkbox');
-
- if (!wonCheckbox || !lostCheckbox) return;
-
- // Синхронизация с текущими значениями полей
- wonCheckbox.checked = frm.doc.won ? true : false;
- lostCheckbox.checked = frm.doc.lost ? true : false;
-
- // Обеспечиваем что всегда выбрана одна галочка
- if (!wonCheckbox.checked && !lostCheckbox.checked) {
- wonCheckbox.checked = true;
- frm.set_value('won', 1);
- }
-
- // Удаляем старые обработчики событий
- wonCheckbox.removeEventListener('change', wonCheckbox._changeHandler);
- lostCheckbox.removeEventListener('change', lostCheckbox._changeHandler);
-
- // Добавляем новые обработчики событий
- wonCheckbox._changeHandler = function() {
- if (this.checked) {
- lostCheckbox.checked = false;
- frm.set_value('lost', 0);
- frm.set_value('won', 1);
- } else {
- lostCheckbox.checked = true;
- frm.set_value('lost', 1);
- frm.set_value('won', 0);
- }
- };
-
- lostCheckbox._changeHandler = function() {
- if (this.checked) {
- wonCheckbox.checked = false;
- frm.set_value('won', 0);
- frm.set_value('lost', 1);
- } else {
- wonCheckbox.checked = true;
- frm.set_value('won', 1);
- frm.set_value('lost', 0);
- }
- };
-
- wonCheckbox.addEventListener('change', wonCheckbox._changeHandler);
- lostCheckbox.addEventListener('change', lostCheckbox._changeHandler);
+// Функция проверки, должны ли быть видимы поля Seller/Organizer (то же самое условие)
+function should_show_seller_organizer(frm) {
+ return is_gambling_company(frm);
}
-// Функция синхронизации HTML чекбоксов со скрытыми полями
-function update_html_checkboxes(frm) {
- setTimeout(function() {
- const wonCheckbox = document.querySelector('.won-checkbox');
- const lostCheckbox = document.querySelector('.lost-checkbox');
+// Функция для применения CSS стилей чтобы чекбоксы были в одну строку
+function apply_inline_checkbox_style(frm) {
+ // Добавляем CSS если еще не добавлен
+ if (!document.getElementById('won-lost-inline-style')) {
+ const style = document.createElement('style');
+ style.id = 'won-lost-inline-style';
+ style.textContent = `
+ /* Делаем Won и Lost в одну строку */
+ [data-fieldname="won"],
+ [data-fieldname="lost"] {
+ display: inline-block !important;
+ width: auto !important;
+ margin-right: 30px !important;
+ vertical-align: top !important;
+ }
- if (wonCheckbox && lostCheckbox) {
- wonCheckbox.checked = frm.doc.won ? true : false;
- lostCheckbox.checked = frm.doc.lost ? true : false;
- }
- }, 100);
+ /* Убираем перенос строки после Won */
+ [data-fieldname="won"] + [data-fieldname="lost"] {
+ margin-left: 0 !important;
+ }
+ `;
+ document.head.appendChild(style);
+ }
}
// Функция синхронизации фактических сумм НДС из items в taxes