mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-13 01:34:10 +00:00
fix(gross-profit): handle returns outside sale period
(cherry picked from commit 67d8223f73)
This commit is contained in:
@@ -5,15 +5,16 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb, scrub
|
from frappe import _, qb, scrub
|
||||||
from frappe.query_builder import Order
|
from frappe.query_builder import Case, Order
|
||||||
|
from frappe.query_builder.functions import Coalesce
|
||||||
from frappe.utils import cint, flt, formatdate
|
from frappe.utils import cint, flt, formatdate
|
||||||
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
get_dimension_with_children,
|
get_dimension_with_children,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
|
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
|
||||||
from erpnext.controllers.queries import get_match_cond
|
|
||||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||||
from erpnext.stock.utils import get_incoming_rate
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
|
|
||||||
@@ -851,129 +852,171 @@ class GrossProfitGenerator:
|
|||||||
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
|
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
|
||||||
|
|
||||||
def load_invoice_items(self):
|
def load_invoice_items(self):
|
||||||
conditions = ""
|
self.si_list = []
|
||||||
if self.filters.company:
|
|
||||||
conditions += " and `tabSales Invoice`.company = %(company)s"
|
SalesInvoice = frappe.qb.DocType("Sales Invoice")
|
||||||
if self.filters.from_date:
|
base_query = self.prepare_invoice_query()
|
||||||
conditions += " and posting_date >= %(from_date)s"
|
|
||||||
if self.filters.to_date:
|
|
||||||
conditions += " and posting_date <= %(to_date)s"
|
|
||||||
|
|
||||||
if self.filters.include_returned_invoices:
|
if self.filters.include_returned_invoices:
|
||||||
conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
|
invoice_query = base_query.where(
|
||||||
|
(SalesInvoice.is_return == 0)
|
||||||
|
| ((SalesInvoice.is_return == 1) & SalesInvoice.return_against.isnull())
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
conditions += " and is_return = 0"
|
invoice_query = base_query.where(SalesInvoice.is_return == 0)
|
||||||
|
|
||||||
if self.filters.item_group:
|
self.si_list += invoice_query.run(as_dict=True)
|
||||||
conditions += f" and {get_item_group_condition(self.filters.item_group)}"
|
self.prepare_vouchers_to_ignore()
|
||||||
|
|
||||||
if self.filters.sales_person:
|
ret_invoice_query = base_query.where(
|
||||||
conditions += """
|
(SalesInvoice.is_return == 1) & SalesInvoice.return_against.isnotnull()
|
||||||
and exists(select 1
|
)
|
||||||
from `tabSales Team` st
|
if self.vouchers_to_ignore:
|
||||||
where st.parent = `tabSales Invoice`.name
|
ret_invoice_query = base_query.where(SalesInvoice.return_against.notin(self.vouchers_to_ignore))
|
||||||
and st.sales_person = %(sales_person)s)
|
|
||||||
"""
|
self.si_list += ret_invoice_query.run(as_dict=True)
|
||||||
|
|
||||||
|
def prepare_invoice_query(self):
|
||||||
|
SalesInvoice = frappe.qb.DocType("Sales Invoice")
|
||||||
|
SalesInvoiceItem = frappe.qb.DocType("Sales Invoice Item")
|
||||||
|
Item = frappe.qb.DocType("Item")
|
||||||
|
SalesTeam = frappe.qb.DocType("Sales Team")
|
||||||
|
PaymentSchedule = frappe.qb.DocType("Payment Schedule")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(SalesInvoice)
|
||||||
|
.join(SalesInvoiceItem)
|
||||||
|
.on(SalesInvoiceItem.parent == SalesInvoice.name)
|
||||||
|
.join(Item)
|
||||||
|
.on(Item.name == SalesInvoiceItem.item_code)
|
||||||
|
.where((SalesInvoice.docstatus == 1) & (SalesInvoice.is_opening != "Yes"))
|
||||||
|
)
|
||||||
|
|
||||||
|
query = self.apply_common_filters(query, SalesInvoice, SalesInvoiceItem, SalesTeam)
|
||||||
|
|
||||||
|
query = query.select(
|
||||||
|
SalesInvoiceItem.parenttype,
|
||||||
|
SalesInvoiceItem.parent,
|
||||||
|
SalesInvoice.posting_date,
|
||||||
|
SalesInvoice.posting_time,
|
||||||
|
SalesInvoice.project,
|
||||||
|
SalesInvoice.update_stock,
|
||||||
|
SalesInvoice.customer,
|
||||||
|
SalesInvoice.customer_group,
|
||||||
|
SalesInvoice.customer_name,
|
||||||
|
SalesInvoice.territory,
|
||||||
|
SalesInvoiceItem.item_code,
|
||||||
|
SalesInvoice.base_net_total.as_("invoice_base_net_total"),
|
||||||
|
SalesInvoiceItem.item_name,
|
||||||
|
SalesInvoiceItem.description,
|
||||||
|
SalesInvoiceItem.warehouse,
|
||||||
|
SalesInvoiceItem.item_group,
|
||||||
|
SalesInvoiceItem.brand,
|
||||||
|
SalesInvoiceItem.so_detail,
|
||||||
|
SalesInvoiceItem.sales_order,
|
||||||
|
SalesInvoiceItem.dn_detail,
|
||||||
|
SalesInvoiceItem.delivery_note,
|
||||||
|
SalesInvoiceItem.stock_qty.as_("qty"),
|
||||||
|
SalesInvoiceItem.base_net_rate,
|
||||||
|
SalesInvoiceItem.base_net_amount,
|
||||||
|
SalesInvoiceItem.name.as_("item_row"),
|
||||||
|
SalesInvoice.is_return,
|
||||||
|
SalesInvoiceItem.cost_center,
|
||||||
|
SalesInvoiceItem.serial_and_batch_bundle,
|
||||||
|
)
|
||||||
|
|
||||||
if self.filters.group_by == "Sales Person":
|
if self.filters.group_by == "Sales Person":
|
||||||
sales_person_cols = """, sales.sales_person,
|
query = query.select(
|
||||||
sales.allocated_percentage * `tabSales Invoice Item`.base_net_amount / 100 as allocated_amount,
|
SalesTeam.sales_person,
|
||||||
sales.incentives
|
(SalesTeam.allocated_percentage * SalesInvoiceItem.base_net_amount / 100).as_(
|
||||||
"""
|
"allocated_amount"
|
||||||
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
|
),
|
||||||
else:
|
SalesTeam.incentives,
|
||||||
sales_person_cols = ""
|
)
|
||||||
sales_team_table = ""
|
|
||||||
|
query = query.left_join(SalesTeam).on(SalesTeam.parent == SalesInvoice.name)
|
||||||
|
|
||||||
if self.filters.group_by == "Payment Term":
|
if self.filters.group_by == "Payment Term":
|
||||||
payment_term_cols = """,if(`tabSales Invoice`.is_return = 1,
|
query = query.select(
|
||||||
'{}',
|
Case()
|
||||||
coalesce(schedule.payment_term, '{}')) as payment_term,
|
.when(SalesInvoice.is_return == 1, _("Sales Return"))
|
||||||
schedule.invoice_portion,
|
.else_(Coalesce(PaymentSchedule.payment_term, _("No Terms")))
|
||||||
schedule.payment_amount """.format(_("Sales Return"), _("No Terms"))
|
.as_("payment_term"),
|
||||||
payment_term_table = """ left join `tabPayment Schedule` schedule on schedule.parent = `tabSales Invoice`.name and
|
PaymentSchedule.invoice_portion,
|
||||||
`tabSales Invoice`.is_return = 0 """
|
PaymentSchedule.payment_amount,
|
||||||
else:
|
)
|
||||||
payment_term_cols = ""
|
|
||||||
payment_term_table = ""
|
|
||||||
|
|
||||||
if self.filters.get("sales_invoice"):
|
query = query.left_join(PaymentSchedule).on(
|
||||||
conditions += " and `tabSales Invoice`.name = %(sales_invoice)s"
|
(PaymentSchedule.parent == SalesInvoice.name) & (SalesInvoice.is_return == 0)
|
||||||
|
)
|
||||||
|
|
||||||
if self.filters.get("item_code"):
|
query = query.orderby(SalesInvoice.posting_date, order=Order.desc).orderby(
|
||||||
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
|
SalesInvoice.posting_time, order=Order.desc
|
||||||
|
)
|
||||||
|
|
||||||
if self.filters.get("cost_center"):
|
return query
|
||||||
|
|
||||||
|
def apply_common_filters(self, query, SalesInvoice, SalesInvoiceItem, SalesTeam):
|
||||||
|
if self.filters.company:
|
||||||
|
query = query.where(SalesInvoice.company == self.filters.company)
|
||||||
|
|
||||||
|
if self.filters.from_date:
|
||||||
|
query = query.where(SalesInvoice.posting_date >= self.filters.from_date)
|
||||||
|
|
||||||
|
if self.filters.to_date:
|
||||||
|
query = query.where(SalesInvoice.posting_date <= self.filters.to_date)
|
||||||
|
|
||||||
|
if self.filters.item_group:
|
||||||
|
query = query.where(get_item_group_condition(self.filters.item_group))
|
||||||
|
|
||||||
|
if self.filters.sales_person:
|
||||||
|
query = query.where(
|
||||||
|
ExistsCriterion(
|
||||||
|
frappe.qb.from_(SalesTeam)
|
||||||
|
.select(1)
|
||||||
|
.where(
|
||||||
|
(SalesTeam.parent == SalesInvoice.name)
|
||||||
|
& (SalesTeam.sales_person == self.filters.sales_person)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.filters.sales_invoice:
|
||||||
|
query = query.where(SalesInvoice.name == self.filters.sales_invoice)
|
||||||
|
|
||||||
|
if self.filters.item_code:
|
||||||
|
query = query.where(SalesInvoiceItem.item_code == self.filters.item_code)
|
||||||
|
|
||||||
|
if self.filters.cost_center:
|
||||||
self.filters.cost_center = frappe.parse_json(self.filters.get("cost_center"))
|
self.filters.cost_center = frappe.parse_json(self.filters.get("cost_center"))
|
||||||
self.filters.cost_center = get_cost_centers_with_children(self.filters.cost_center)
|
self.filters.cost_center = get_cost_centers_with_children(self.filters.cost_center)
|
||||||
conditions += " and `tabSales Invoice Item`.cost_center in %(cost_center)s"
|
query = query.where(SalesInvoiceItem.cost_center.isin(self.filters.cost_center))
|
||||||
|
|
||||||
if self.filters.get("project"):
|
if self.filters.project:
|
||||||
self.filters.project = frappe.parse_json(self.filters.get("project"))
|
self.filters.project = frappe.parse_json(self.filters.get("project"))
|
||||||
conditions += " and `tabSales Invoice Item`.project in %(project)s"
|
query = query.where(SalesInvoiceItem.project.isin(self.filters.project))
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
for dim in get_accounting_dimensions(as_list=False) or []:
|
||||||
if accounting_dimensions:
|
if self.filters.get(dim.fieldname):
|
||||||
for dimension in accounting_dimensions:
|
if frappe.get_cached_value("DocType", dim.document_type, "is_tree"):
|
||||||
if self.filters.get(dimension.fieldname):
|
self.filters[dim.fieldname] = get_dimension_with_children(
|
||||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
dim.document_type, self.filters.get(dim.fieldname)
|
||||||
self.filters[dimension.fieldname] = get_dimension_with_children(
|
)
|
||||||
dimension.document_type, self.filters.get(dimension.fieldname)
|
query = query.where(SalesInvoiceItem[dim.fieldname].isin(self.filters[dim.fieldname]))
|
||||||
)
|
|
||||||
conditions += (
|
|
||||||
f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
conditions += (
|
|
||||||
f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.filters.get("warehouse"):
|
if self.filters.warehouse:
|
||||||
warehouse_details = frappe.db.get_value(
|
lft, rgt = frappe.db.get_value("Warehouse", self.filters.warehouse, ["lft", "rgt"])
|
||||||
"Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
WH = frappe.qb.DocType("Warehouse")
|
||||||
|
query = query.where(
|
||||||
|
SalesInvoiceItem.warehouse.isin(
|
||||||
|
frappe.qb.from_(WH).select(WH.name).where((WH.lft >= lft) & (WH.rgt <= rgt))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if warehouse_details:
|
|
||||||
conditions += f" and `tabSales Invoice Item`.warehouse in (select name from `tabWarehouse` wh where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
|
|
||||||
|
|
||||||
self.si_list = frappe.db.sql(
|
return query
|
||||||
"""
|
|
||||||
select
|
def prepare_vouchers_to_ignore(self):
|
||||||
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
|
self.vouchers_to_ignore = tuple(row["parent"] for row in self.si_list)
|
||||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
|
|
||||||
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
|
||||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.customer_name,
|
|
||||||
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
|
||||||
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
|
|
||||||
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
|
||||||
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
|
|
||||||
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
|
|
||||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
|
|
||||||
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
|
||||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
|
||||||
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
|
|
||||||
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.serial_and_batch_bundle
|
|
||||||
{sales_person_cols}
|
|
||||||
{payment_term_cols}
|
|
||||||
from
|
|
||||||
`tabSales Invoice` inner join `tabSales Invoice Item`
|
|
||||||
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
|
|
||||||
join `tabItem` item on item.name = `tabSales Invoice Item`.item_code
|
|
||||||
{sales_team_table}
|
|
||||||
{payment_term_table}
|
|
||||||
where
|
|
||||||
`tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond}
|
|
||||||
order by
|
|
||||||
`tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc""".format(
|
|
||||||
conditions=conditions,
|
|
||||||
sales_person_cols=sales_person_cols,
|
|
||||||
sales_team_table=sales_team_table,
|
|
||||||
payment_term_cols=payment_term_cols,
|
|
||||||
payment_term_table=payment_term_table,
|
|
||||||
match_cond=get_match_cond("Sales Invoice"),
|
|
||||||
),
|
|
||||||
self.filters,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_delivery_notes(self):
|
def get_delivery_notes(self):
|
||||||
self.delivery_notes = frappe._dict({})
|
self.delivery_notes = frappe._dict({})
|
||||||
|
|||||||
Reference in New Issue
Block a user