From d61275ca85f74d121f49a14f7149f9f510a6865c Mon Sep 17 00:00:00 2001
From: Ali <010109ali@gmail.com>
Date: Mon, 6 Oct 2025 20:43:15 +0400
Subject: [PATCH] fixed cob report
---
.../customer_outstanding_balance.js | 81 ++--
.../customer_outstanding_balance.py | 364 +++++++++++-------
2 files changed, 290 insertions(+), 155 deletions(-)
diff --git a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js
index ad67c2c..ac4d7f7 100644
--- a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js
+++ b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js
@@ -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 = "" + value + "";
}
- // Highlight payments in green
- if (column.fieldname == "payments" && data && data.payments > 0) {
+ if (column.fieldname == "payments" && data.payments > 0) {
value = "" + value + "";
}
- // 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 = "" + value + "";
}
- // 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 = "" + value + "";
}
- // Highlight VAT allocation columns
- if (column.fieldname == "edv_daxil_18" && data && data.edv_daxil_18 > 0) {
- value = "" + value + "";
+ // Tax template columns highlighting
+ // ƏDV 18% - Purple tones
+ if (column.fieldname == "edv_18_opening" && data.edv_18_opening > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_18_plus" && data.edv_18_plus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_18_minus" && data.edv_18_minus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_18_closing" && data.edv_18_closing > 0) {
+ value = "" + value + "";
}
- if (column.fieldname == "edv_azadolma" && data && data.edv_azadolma > 0) {
- value = "" + value + "";
+ // ƏDV daxil 18% - Blue-green tones
+ if (column.fieldname == "edv_daxil_18_opening" && data.edv_daxil_18_opening > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_daxil_18_plus" && data.edv_daxil_18_plus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_daxil_18_minus" && data.edv_daxil_18_minus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_daxil_18_closing" && data.edv_daxil_18_closing > 0) {
+ value = "" + value + "";
}
- if (column.fieldname == "edv_0" && data && data.edv_0 > 0) {
- value = "" + value + "";
+ // ƏDV 0% - Green tones
+ if (column.fieldname == "edv_0_opening" && data.edv_0_opening > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_0_plus" && data.edv_0_plus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_0_minus" && data.edv_0_minus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_0_closing" && data.edv_0_closing > 0) {
+ value = "" + value + "";
}
- if (column.fieldname == "edv_18" && data && data.edv_18 > 0) {
- value = "" + value + "";
+ // ƏDV-dən azadolma - Pink-red tones
+ if (column.fieldname == "edv_azadolma_opening" && data.edv_azadolma_opening > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_azadolma_plus" && data.edv_azadolma_plus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_azadolma_minus" && data.edv_azadolma_minus > 0) {
+ value = "" + value + "";
+ }
+ if (column.fieldname == "edv_azadolma_closing" && data.edv_azadolma_closing > 0) {
+ value = "" + value + "";
}
return value;
diff --git a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py
index 27244a0..a6a54a5 100644
--- a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py
+++ b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py
@@ -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'])