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(), "default": frappe.datetime.get_today(),
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "customer",
"label": __("Customer"),
"fieldtype": "MultiSelectList",
"get_data": function (txt) {
return frappe.db.get_link_options("Customer", txt);
}
},
{ {
"fieldname": "customer_group", "fieldname": "customer_group",
"label": __("Customer Group"), "label": __("Customer Group"),
@ -44,41 +36,80 @@ frappe.query_reports["Customer Outstanding Balance"] = {
"formatter": function (value, row, column, data, default_formatter) { "formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data); value = default_formatter(value, row, column, data);
// Highlight opening balance in blue if (!data) return value;
if (column.fieldname == "opening_balance" && data && data.opening_balance > 0) {
// Original columns highlighting
if (column.fieldname == "opening_balance" && data.opening_balance > 0) {
value = "<span style='color: #1f77b4; font-weight: bold;'>" + value + "</span>"; value = "<span style='color: #1f77b4; font-weight: bold;'>" + value + "</span>";
} }
// Highlight payments in green if (column.fieldname == "payments" && data.payments > 0) {
if (column.fieldname == "payments" && data && data.payments > 0) {
value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>"; value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>";
} }
// Highlight new invoices in orange if (column.fieldname == "new_invoices" && data.new_invoices > 0) {
if (column.fieldname == "new_invoices" && data && data.new_invoices > 0) {
value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>"; value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>";
} }
// Highlight closing balance in dark red if (column.fieldname == "closing_balance" && data.closing_balance > 0) {
if (column.fieldname == "closing_balance" && data && data.closing_balance > 0) {
value = "<span style='color: #d62728; font-weight: bold;'>" + value + "</span>"; value = "<span style='color: #d62728; font-weight: bold;'>" + value + "</span>";
} }
// Highlight VAT allocation columns // Tax template columns highlighting
if (column.fieldname == "edv_daxil_18" && data && data.edv_daxil_18 > 0) { // ƏDV 18% - Purple tones
value = "<span style='color: #17a2b8; font-weight: bold;'>" + value + "</span>"; 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) { // ƏDV daxil 18% - Blue-green tones
value = "<span style='color: #28a745; font-weight: bold;'>" + value + "</span>"; 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) { // ƏDV 0% - Green tones
value = "<span style='color: #ffc107; font-weight: bold;'>" + value + "</span>"; 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) { // ƏDV-dən azadolma - Pink-red tones
value = "<span style='color: #dc3545; font-weight: bold;'>" + value + "</span>"; 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; return value;

View File

@ -4,6 +4,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from datetime import datetime from datetime import datetime
from dateutil.relativedelta import relativedelta
def execute(filters=None): def execute(filters=None):
"""Main function for report execution""" """Main function for report execution"""
@ -13,8 +14,8 @@ def execute(filters=None):
return columns, data return columns, data
def get_columns(): def get_columns():
"""Get report columns definition""" """Get report columns definition with 16 new columns for tax templates"""
return [ columns = [
{ {
"label": _("Customer"), "label": _("Customer"),
"fieldname": "customer", "fieldname": "customer",
@ -51,38 +52,64 @@ def get_columns():
"fieldname": "closing_balance", "fieldname": "closing_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 140 "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): def get_data(filters):
"""Get report data""" """Get report data from VAT Allocation documents"""
conditions = get_conditions(filters) 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""" opening_balance_data = frappe.db.sql(f"""
SELECT SELECT
ple.party as customer, ple.party as customer,
@ -103,7 +130,7 @@ def get_data(filters):
ABS(opening_balance) >= 0.01 ABS(opening_balance) >= 0.01
""", filters, as_dict=1) """, filters, as_dict=1)
# Get movements during the period # Get movements during the period - original ledger-based calculation
period_data = frappe.db.sql(f""" period_data = frappe.db.sql(f"""
SELECT SELECT
ple.party as customer, ple.party as customer,
@ -124,7 +151,7 @@ def get_data(filters):
ple.party ple.party
""", filters, as_dict=1) """, 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""" closing_balance_data = frappe.db.sql(f"""
SELECT SELECT
ple.party as customer, ple.party as customer,
@ -145,11 +172,16 @@ def get_data(filters):
ABS(closing_balance) >= 0.01 ABS(closing_balance) >= 0.01
""", filters, as_dict=1) """, filters, as_dict=1)
# Get VAT allocation data # Get VAT Allocation data for tax templates
vat_data = get_vat_allocation_data(filters) 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 # 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 # Get customer names and other details
add_customer_details(result) add_customer_details(result)
@ -160,77 +192,114 @@ def get_conditions(filters):
"""Build WHERE conditions based on filters""" """Build WHERE conditions based on filters"""
conditions = "" conditions = ""
if filters.get("customer"):
customer_list = "', '".join(filters.get("customer"))
conditions += f" AND ple.party IN ('{customer_list}')"
if filters.get("customer_group"): if filters.get("customer_group"):
conditions += " AND ple.party IN (SELECT name FROM `tabCustomer` WHERE customer_group = %(customer_group)s)" conditions += " AND ple.party IN (SELECT name FROM `tabCustomer` WHERE customer_group = %(customer_group)s)"
return conditions return conditions
def get_vat_allocation_data(filters): def get_vat_allocation_data(year, month, company):
"""Get VAT allocation data for customers in the period""" """
if not filters.get("from_date") or not filters.get("to_date"): Get VAT allocation data for specified year and month
return [] 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 if not frappe.db.exists("VAT allocation", vat_name):
from_date = datetime.strptime(str(filters.get("from_date")), "%Y-%m-%d") return {}
to_date = datetime.strptime(str(filters.get("to_date")), "%Y-%m-%d")
# Get month name mapping # Get sales invoice table data from VAT allocation
month_names = [ sales_data = frappe.db.sql("""
"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 = """
SELECT SELECT
vasi.customer, customer,
vasi.item_tax_template, item_tax_template,
SUM(vasi.allocated_amount) as allocated_amount SUM(unallocated_amount) as total_unallocated,
SUM(allocated_amount) as total_allocated
FROM FROM
`tabVAT allocation sales invoice` vasi `tabVAT allocation sales invoice`
INNER JOIN
`tabVAT allocation` va ON va.name = vasi.parent
WHERE WHERE
va.docstatus = 1 parent = %(vat_name)s
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 + """
GROUP BY 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, { # Organize data by customer and tax template
"year": year, result = {}
"month": month, for row in sales_data:
"customer_group": filters.get("customer_group") customer = row['customer']
}, as_dict=1) 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""" """Combine all datasets into final result"""
result_dict = {} 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: for row in opening_data:
customer = row['customer'] customer = row['customer']
result_dict[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, 'opening_balance': row['opening_balance'] or 0.0,
'payments': 0.0, 'payments': 0.0,
'new_invoices': 0.0, 'new_invoices': 0.0,
'closing_balance': 0.0, 'closing_balance': 0.0
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 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: for row in period_data:
customer = row['customer'] customer = row['customer']
if customer not in result_dict: 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, 'opening_balance': 0.0,
'payments': 0.0, 'payments': 0.0,
'new_invoices': 0.0, 'new_invoices': 0.0,
'closing_balance': 0.0, 'closing_balance': 0.0
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 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]['payments'] = row['payments'] or 0.0
result_dict[customer]['new_invoices'] = row['new_invoices'] 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: for row in closing_data:
customer = row['customer'] customer = row['customer']
if customer not in result_dict: 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, 'opening_balance': 0.0,
'payments': 0.0, 'payments': 0.0,
'new_invoices': 0.0, 'new_invoices': 0.0,
'closing_balance': 0.0, 'closing_balance': 0.0
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 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 result_dict[customer]['closing_balance'] = row['closing_balance'] or 0.0
# Add VAT allocation data # Get all customers from all sources
for row in vat_data: all_customers = set(result_dict.keys())
customer = row['customer'] 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: if customer not in result_dict:
result_dict[customer] = { result_dict[customer] = {
'customer': customer, 'customer': customer,
'opening_balance': 0.0, 'opening_balance': 0.0,
'payments': 0.0, 'payments': 0.0,
'new_invoices': 0.0, 'new_invoices': 0.0,
'closing_balance': 0.0, 'closing_balance': 0.0
'edv_daxil_18': 0.0,
'edv_azadolma': 0.0,
'edv_0': 0.0,
'edv_18': 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 # Get data for this customer
template = row['item_tax_template'] customer_current = vat_current.get(customer, {})
allocated = row['allocated_amount'] or 0.0 customer_previous = vat_previous.get(customer, {})
customer_si = si_current.get(customer, {})
if 'daxil 18' in template or 'daxil18' in template.lower().replace(' ', ''): for template_name, prefix in template_map.items():
result_dict[customer]['edv_daxil_18'] += allocated # Previous month data (for opening balance)
elif 'azadolma' in template.lower(): prev_data = customer_previous.get(template_name, {'unallocated': 0.0, 'allocated': 0.0})
result_dict[customer]['edv_azadolma'] += allocated opening = prev_data['unallocated'] # Closing of previous month = unallocated amount
elif '0%' in template or 'edv 0' in template.lower():
result_dict[customer]['edv_0'] += allocated # Current month data
elif '18%' in template or 'edv 18' in template.lower(): curr_data = customer_current.get(template_name, {'unallocated': 0.0, 'allocated': 0.0})
result_dict[customer]['edv_18'] += allocated 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 # Convert to list and filter out zero balances
result = [] result = []
for customer, data in result_dict.items(): for customer, data in result_dict.items():
# Only include if there's some activity or outstanding balance # Check if there's any activity
if (abs(data['opening_balance']) >= 0.01 or has_activity = (
abs(data['opening_balance']) >= 0.01 or
abs(data['closing_balance']) >= 0.01 or abs(data['closing_balance']) >= 0.01 or
abs(data['new_invoices']) >= 0.01 or abs(data['new_invoices']) >= 0.01 or
abs(data['payments']) >= 0.01 or abs(data['payments']) >= 0.01
abs(data['edv_daxil_18']) >= 0.01 or )
abs(data['edv_azadolma']) >= 0.01 or
abs(data['edv_0']) >= 0.01 or # Also check tax template activity
abs(data['edv_18']) >= 0.01): 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) result.append(data)
return sorted(result, key=lambda x: x['customer']) return sorted(result, key=lambda x: x['customer'])