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'])