fixed cob report

This commit is contained in:
Ali 2025-10-06 20:43:15 +04:00
parent c22dabf943
commit d61275ca85
2 changed files with 290 additions and 155 deletions

View File

@ -25,14 +25,6 @@ frappe.query_reports["Customer Outstanding Balance"] = {
"default": frappe.datetime.get_today(),
"reqd": 1
},
{
"fieldname": "customer",
"label": __("Customer"),
"fieldtype": "MultiSelectList",
"get_data": function (txt) {
return frappe.db.get_link_options("Customer", txt);
}
},
{
"fieldname": "customer_group",
"label": __("Customer Group"),
@ -44,41 +36,80 @@ frappe.query_reports["Customer Outstanding Balance"] = {
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
// Highlight opening balance in blue
if (column.fieldname == "opening_balance" && data && data.opening_balance > 0) {
if (!data) return value;
// Original columns highlighting
if (column.fieldname == "opening_balance" && data.opening_balance > 0) {
value = "<span style='color: #1f77b4; font-weight: bold;'>" + value + "</span>";
}
// Highlight payments in green
if (column.fieldname == "payments" && data && data.payments > 0) {
if (column.fieldname == "payments" && data.payments > 0) {
value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>";
}
// Highlight new invoices in orange
if (column.fieldname == "new_invoices" && data && data.new_invoices > 0) {
if (column.fieldname == "new_invoices" && data.new_invoices > 0) {
value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>";
}
// Highlight closing balance in dark red
if (column.fieldname == "closing_balance" && data && data.closing_balance > 0) {
if (column.fieldname == "closing_balance" && data.closing_balance > 0) {
value = "<span style='color: #d62728; font-weight: bold;'>" + value + "</span>";
}
// Highlight VAT allocation columns
if (column.fieldname == "edv_daxil_18" && data && data.edv_daxil_18 > 0) {
value = "<span style='color: #17a2b8; font-weight: bold;'>" + value + "</span>";
// Tax template columns highlighting
// ƏDV 18% - Purple tones
if (column.fieldname == "edv_18_opening" && data.edv_18_opening > 0) {
value = "<span style='color: #9467bd; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_18_plus" && data.edv_18_plus > 0) {
value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_18_minus" && data.edv_18_minus > 0) {
value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_18_closing" && data.edv_18_closing > 0) {
value = "<span style='color: #8c564b; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_azadolma" && data && data.edv_azadolma > 0) {
value = "<span style='color: #28a745; font-weight: bold;'>" + value + "</span>";
// ƏDV daxil 18% - Blue-green tones
if (column.fieldname == "edv_daxil_18_opening" && data.edv_daxil_18_opening > 0) {
value = "<span style='color: #17becf; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_daxil_18_plus" && data.edv_daxil_18_plus > 0) {
value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_daxil_18_minus" && data.edv_daxil_18_minus > 0) {
value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_daxil_18_closing" && data.edv_daxil_18_closing > 0) {
value = "<span style='color: #1f77b4; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_0" && data && data.edv_0 > 0) {
value = "<span style='color: #ffc107; font-weight: bold;'>" + value + "</span>";
// ƏDV 0% - Green tones
if (column.fieldname == "edv_0_opening" && data.edv_0_opening > 0) {
value = "<span style='color: #bcbd22; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_0_plus" && data.edv_0_plus > 0) {
value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_0_minus" && data.edv_0_minus > 0) {
value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_0_closing" && data.edv_0_closing > 0) {
value = "<span style='color: #7f7f7f; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_18" && data && data.edv_18 > 0) {
value = "<span style='color: #dc3545; font-weight: bold;'>" + value + "</span>";
// ƏDV-dən azadolma - Pink-red tones
if (column.fieldname == "edv_azadolma_opening" && data.edv_azadolma_opening > 0) {
value = "<span style='color: #e377c2; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_azadolma_plus" && data.edv_azadolma_plus > 0) {
value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_azadolma_minus" && data.edv_azadolma_minus > 0) {
value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>";
}
if (column.fieldname == "edv_azadolma_closing" && data.edv_azadolma_closing > 0) {
value = "<span style='color: #d62728; font-weight: bold;'>" + value + "</span>";
}
return value;

View File

@ -4,6 +4,7 @@
import frappe
from frappe import _
from datetime import datetime
from dateutil.relativedelta import relativedelta
def execute(filters=None):
"""Main function for report execution"""
@ -13,8 +14,8 @@ def execute(filters=None):
return columns, data
def get_columns():
"""Get report columns definition"""
return [
"""Get report columns definition with 16 new columns for tax templates"""
columns = [
{
"label": _("Customer"),
"fieldname": "customer",
@ -51,38 +52,64 @@ def get_columns():
"fieldname": "closing_balance",
"fieldtype": "Currency",
"width": 140
},
{
"label": _("ƏDV daxil 18%"),
"fieldname": "edv_daxil_18",
"fieldtype": "Currency",
"width": 140
},
{
"label": _("ƏDV-dən azadolma"),
"fieldname": "edv_azadolma",
"fieldtype": "Currency",
"width": 140
},
{
"label": _("ƏDV 0%"),
"fieldname": "edv_0",
"fieldtype": "Currency",
"width": 140
},
{
"label": _("ƏDV 18%"),
"fieldname": "edv_18",
"fieldtype": "Currency",
"width": 140
}
]
# Add 16 columns for 4 tax templates
tax_templates = [
{"name": "ƏDV 18%", "prefix": "edv_18"},
{"name": "ƏDV daxil 18%", "prefix": "edv_daxil_18"},
{"name": "ƏDV 0%", "prefix": "edv_0"},
{"name": "ƏDV-dən azadolma", "prefix": "edv_azadolma"}
]
for template in tax_templates:
columns.extend([
{
"label": _(f"{template['name']} Opening"),
"fieldname": f"{template['prefix']}_opening",
"fieldtype": "Currency",
"width": 130
},
{
"label": _(f"{template['name']} +"),
"fieldname": f"{template['prefix']}_plus",
"fieldtype": "Currency",
"width": 130
},
{
"label": _(f"{template['name']} -"),
"fieldname": f"{template['prefix']}_minus",
"fieldtype": "Currency",
"width": 130
},
{
"label": _(f"{template['name']} Closing"),
"fieldname": f"{template['prefix']}_closing",
"fieldtype": "Currency",
"width": 130
}
])
return columns
def get_data(filters):
"""Get report data"""
"""Get report data from VAT Allocation documents"""
conditions = get_conditions(filters)
# Get opening balance (before from_date)
# Get current period VAT Allocation
from_date = datetime.strptime(filters.get('from_date'), '%Y-%m-%d')
to_date = datetime.strptime(filters.get('to_date'), '%Y-%m-%d')
current_year = from_date.year
current_month = from_date.strftime('%B') # Full month name
# Get previous month for opening balance
previous_date = from_date - relativedelta(months=1)
previous_year = previous_date.year
previous_month = previous_date.strftime('%B')
# Get opening balance (before from_date) - original ledger-based calculation
opening_balance_data = frappe.db.sql(f"""
SELECT
ple.party as customer,
@ -103,7 +130,7 @@ def get_data(filters):
ABS(opening_balance) >= 0.01
""", filters, as_dict=1)
# Get movements during the period
# Get movements during the period - original ledger-based calculation
period_data = frappe.db.sql(f"""
SELECT
ple.party as customer,
@ -124,7 +151,7 @@ def get_data(filters):
ple.party
""", filters, as_dict=1)
# Get closing balance (up to to_date)
# Get closing balance (up to to_date) - original ledger-based calculation
closing_balance_data = frappe.db.sql(f"""
SELECT
ple.party as customer,
@ -145,11 +172,16 @@ def get_data(filters):
ABS(closing_balance) >= 0.01
""", filters, as_dict=1)
# Get VAT allocation data
vat_data = get_vat_allocation_data(filters)
# Get VAT Allocation data for tax templates
vat_current = get_vat_allocation_data(current_year, current_month, filters.get('company'))
vat_previous = get_vat_allocation_data(previous_year, previous_month, filters.get('company'))
# Get Sales Invoice data directly for current month (for "+" column)
si_current = get_sales_invoice_data(current_year, current_month, filters.get('company'))
# Combine all data
result = combine_data(opening_balance_data, period_data, closing_balance_data, vat_data)
result = combine_data(opening_balance_data, period_data, closing_balance_data,
vat_current, vat_previous, si_current)
# Get customer names and other details
add_customer_details(result)
@ -160,77 +192,114 @@ def get_conditions(filters):
"""Build WHERE conditions based on filters"""
conditions = ""
if filters.get("customer"):
customer_list = "', '".join(filters.get("customer"))
conditions += f" AND ple.party IN ('{customer_list}')"
if filters.get("customer_group"):
conditions += " AND ple.party IN (SELECT name FROM `tabCustomer` WHERE customer_group = %(customer_group)s)"
return conditions
def get_vat_allocation_data(filters):
"""Get VAT allocation data for customers in the period"""
if not filters.get("from_date") or not filters.get("to_date"):
return []
def get_vat_allocation_data(year, month, company):
"""
Get VAT allocation data for specified year and month
Returns dict with customer -> tax_template -> {unallocated, allocated}
"""
# Find VAT Allocation document
vat_name = f"VAT-{year}-{month}"
# Determine year and month from the date range
from_date = datetime.strptime(str(filters.get("from_date")), "%Y-%m-%d")
to_date = datetime.strptime(str(filters.get("to_date")), "%Y-%m-%d")
if not frappe.db.exists("VAT allocation", vat_name):
return {}
# Get month name mapping
month_names = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
]
# Use the to_date for determining the period
year = to_date.year
month = month_names[to_date.month - 1]
# Query VAT allocation sales invoice data from submitted documents
vat_query = """
# Get sales invoice table data from VAT allocation
sales_data = frappe.db.sql("""
SELECT
vasi.customer,
vasi.item_tax_template,
SUM(vasi.allocated_amount) as allocated_amount
customer,
item_tax_template,
SUM(unallocated_amount) as total_unallocated,
SUM(allocated_amount) as total_allocated
FROM
`tabVAT allocation sales invoice` vasi
INNER JOIN
`tabVAT allocation` va ON va.name = vasi.parent
`tabVAT allocation sales invoice`
WHERE
va.docstatus = 1
AND va.year = %(year)s
AND va.month = %(month)s
"""
# Add customer filter if specified
customer_condition = ""
if filters.get("customer"):
customer_list = "', '".join(filters.get("customer"))
customer_condition = f" AND vasi.customer IN ('{customer_list}')"
if filters.get("customer_group"):
customer_condition += " AND vasi.customer IN (SELECT name FROM `tabCustomer` WHERE customer_group = %(customer_group)s)"
vat_query += customer_condition + """
parent = %(vat_name)s
GROUP BY
vasi.customer, vasi.item_tax_template
"""
customer, item_tax_template
""", {"vat_name": vat_name}, as_dict=1)
vat_data = frappe.db.sql(vat_query, {
"year": year,
"month": month,
"customer_group": filters.get("customer_group")
}, as_dict=1)
# Organize data by customer and tax template
result = {}
for row in sales_data:
customer = row['customer']
template = row['item_tax_template']
if customer not in result:
result[customer] = {}
result[customer][template] = {
'unallocated': row['total_unallocated'] or 0.0,
'allocated': row['total_allocated'] or 0.0
}
return vat_data
return result
def combine_data(opening_data, period_data, closing_data, vat_data):
def get_sales_invoice_data(year, month, company):
"""
Get Sales Invoice data directly for the specified month
Returns dict with customer -> tax_template -> amount
"""
# Convert month name to number
months = {
"January": 1, "February": 2, "March": 3, "April": 4,
"May": 5, "June": 6, "July": 7, "August": 8,
"September": 9, "October": 10, "November": 11, "December": 12
}
month_number = months.get(month)
if not month_number:
return {}
# Get Sales Invoice items directly
invoice_data = frappe.db.sql("""
SELECT
si.customer,
sii.item_tax_template,
SUM(sii.amount) as total_amount
FROM
`tabSales Invoice` si
INNER JOIN
`tabSales Invoice Item` sii ON si.name = sii.parent
WHERE
si.docstatus = 1
AND YEAR(si.posting_date) = %(year)s
AND MONTH(si.posting_date) = %(month)s
AND si.company = %(company)s
GROUP BY
si.customer, sii.item_tax_template
""", {"year": year, "month": month_number, "company": company}, as_dict=1)
# Organize data by customer and tax template
result = {}
for row in invoice_data:
customer = row['customer']
template = row['item_tax_template']
if customer not in result:
result[customer] = {}
result[customer][template] = row['total_amount'] or 0.0
return result
def combine_data(opening_data, period_data, closing_data, vat_current, vat_previous, si_current):
"""Combine all datasets into final result"""
result_dict = {}
# Add opening balances
# Tax template mapping
template_map = {
"ƏDV 18%": "edv_18",
"ƏDV daxil 18%": "edv_daxil_18",
"ƏDV 0%": "edv_0",
"ƏDV-dən azadolma": "edv_azadolma"
}
# Add opening balances (original logic)
for row in opening_data:
customer = row['customer']
result_dict[customer] = {
@ -238,14 +307,16 @@ def combine_data(opening_data, period_data, closing_data, vat_data):
'opening_balance': row['opening_balance'] or 0.0,
'payments': 0.0,
'new_invoices': 0.0,
'closing_balance': 0.0,
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 0.0
'closing_balance': 0.0
}
# Initialize tax template fields
for prefix in template_map.values():
result_dict[customer][f'{prefix}_opening'] = 0.0
result_dict[customer][f'{prefix}_plus'] = 0.0
result_dict[customer][f'{prefix}_minus'] = 0.0
result_dict[customer][f'{prefix}_closing'] = 0.0
# Add period movements
# Add period movements (original logic)
for row in period_data:
customer = row['customer']
if customer not in result_dict:
@ -254,17 +325,19 @@ def combine_data(opening_data, period_data, closing_data, vat_data):
'opening_balance': 0.0,
'payments': 0.0,
'new_invoices': 0.0,
'closing_balance': 0.0,
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 0.0
'closing_balance': 0.0
}
# Initialize tax template fields
for prefix in template_map.values():
result_dict[customer][f'{prefix}_opening'] = 0.0
result_dict[customer][f'{prefix}_plus'] = 0.0
result_dict[customer][f'{prefix}_minus'] = 0.0
result_dict[customer][f'{prefix}_closing'] = 0.0
result_dict[customer]['payments'] = row['payments'] or 0.0
result_dict[customer]['new_invoices'] = row['new_invoices'] or 0.0
# Add closing balances
# Add closing balances (original logic)
for row in closing_data:
customer = row['customer']
if customer not in result_dict:
@ -273,56 +346,87 @@ def combine_data(opening_data, period_data, closing_data, vat_data):
'opening_balance': 0.0,
'payments': 0.0,
'new_invoices': 0.0,
'closing_balance': 0.0,
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 0.0
'closing_balance': 0.0
}
# Initialize tax template fields
for prefix in template_map.values():
result_dict[customer][f'{prefix}_opening'] = 0.0
result_dict[customer][f'{prefix}_plus'] = 0.0
result_dict[customer][f'{prefix}_minus'] = 0.0
result_dict[customer][f'{prefix}_closing'] = 0.0
result_dict[customer]['closing_balance'] = row['closing_balance'] or 0.0
# Add VAT allocation data
for row in vat_data:
customer = row['customer']
# Get all customers from all sources
all_customers = set(result_dict.keys())
all_customers.update(vat_current.keys())
all_customers.update(vat_previous.keys())
all_customers.update(si_current.keys())
# Add VAT allocation data for tax templates
for customer in all_customers:
# Ensure customer exists in result_dict
if customer not in result_dict:
result_dict[customer] = {
'customer': customer,
'opening_balance': 0.0,
'payments': 0.0,
'new_invoices': 0.0,
'closing_balance': 0.0,
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 0.0
'closing_balance': 0.0
}
# Initialize tax template fields
for prefix in template_map.values():
result_dict[customer][f'{prefix}_opening'] = 0.0
result_dict[customer][f'{prefix}_plus'] = 0.0
result_dict[customer][f'{prefix}_minus'] = 0.0
result_dict[customer][f'{prefix}_closing'] = 0.0
# Map item tax template to field names
template = row['item_tax_template']
allocated = row['allocated_amount'] or 0.0
# Get data for this customer
customer_current = vat_current.get(customer, {})
customer_previous = vat_previous.get(customer, {})
customer_si = si_current.get(customer, {})
if 'daxil 18' in template or 'daxil18' in template.lower().replace(' ', ''):
result_dict[customer]['edv_daxil_18'] += allocated
elif 'azadolma' in template.lower():
result_dict[customer]['edv_azadolma'] += allocated
elif '0%' in template or 'edv 0' in template.lower():
result_dict[customer]['edv_0'] += allocated
elif '18%' in template or 'edv 18' in template.lower():
result_dict[customer]['edv_18'] += allocated
for template_name, prefix in template_map.items():
# Previous month data (for opening balance)
prev_data = customer_previous.get(template_name, {'unallocated': 0.0, 'allocated': 0.0})
opening = prev_data['unallocated'] # Closing of previous month = unallocated amount
# Current month data
curr_data = customer_current.get(template_name, {'unallocated': 0.0, 'allocated': 0.0})
minus = curr_data['allocated'] # Payments (allocated amount)
# Sales Invoice data for "+" (new invoices)
plus = customer_si.get(template_name, 0.0)
# Calculate closing
closing = opening + plus - minus
result_dict[customer][f'{prefix}_opening'] = opening
result_dict[customer][f'{prefix}_plus'] = plus
result_dict[customer][f'{prefix}_minus'] = minus
result_dict[customer][f'{prefix}_closing'] = closing
# Convert to list and filter out zero balances
result = []
for customer, data in result_dict.items():
# Only include if there's some activity or outstanding balance
if (abs(data['opening_balance']) >= 0.01 or
# Check if there's any activity
has_activity = (
abs(data['opening_balance']) >= 0.01 or
abs(data['closing_balance']) >= 0.01 or
abs(data['new_invoices']) >= 0.01 or
abs(data['payments']) >= 0.01 or
abs(data['edv_daxil_18']) >= 0.01 or
abs(data['edv_azadolma']) >= 0.01 or
abs(data['edv_0']) >= 0.01 or
abs(data['edv_18']) >= 0.01):
abs(data['payments']) >= 0.01
)
# Also check tax template activity
for prefix in template_map.values():
if (abs(data[f'{prefix}_opening']) >= 0.01 or
abs(data[f'{prefix}_closing']) >= 0.01 or
abs(data[f'{prefix}_plus']) >= 0.01 or
abs(data[f'{prefix}_minus']) >= 0.01):
has_activity = True
break
if has_activity:
result.append(data)
return sorted(result, key=lambda x: x['customer'])