mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-01 15:47:06 +00:00
Fixes to Return Improvements pull request
- Added "Returned Qty" in Sales and Purchase Order - Map Expense Account in Return Delivery Note - Defined some No Copy fields - Added "Credit Note" and "Debit Note" Print Headings - Fixed patch
This commit is contained in:
@@ -11,10 +11,10 @@ class StockOverReturnError(frappe.ValidationError): pass
|
||||
def validate_return(doc):
|
||||
if not doc.meta.get_field("is_return") or not doc.is_return:
|
||||
return
|
||||
|
||||
|
||||
validate_return_against(doc)
|
||||
validate_returned_items(doc)
|
||||
|
||||
|
||||
def validate_return_against(doc):
|
||||
if not doc.return_against:
|
||||
frappe.throw(_("{0} is mandatory for Return").format(doc.meta.get_label("return_against")))
|
||||
@@ -24,49 +24,49 @@ def validate_return_against(doc):
|
||||
filters["customer"] = doc.customer
|
||||
elif doc.meta.get_field("supplier"):
|
||||
filters["supplier"] = doc.supplier
|
||||
|
||||
|
||||
if not frappe.db.exists(filters):
|
||||
frappe.throw(_("Invalid {0}: {1}")
|
||||
.format(doc.meta.get_label("return_against"), doc.return_against))
|
||||
else:
|
||||
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
|
||||
|
||||
|
||||
# validate posting date time
|
||||
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
|
||||
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
|
||||
|
||||
|
||||
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
|
||||
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
|
||||
|
||||
|
||||
# validate same exchange rate
|
||||
if doc.conversion_rate != ref_doc.conversion_rate:
|
||||
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
|
||||
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
|
||||
|
||||
|
||||
# validate update stock
|
||||
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
|
||||
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
|
||||
.format(doc.return_against))
|
||||
|
||||
|
||||
def validate_returned_items(doc):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
|
||||
valid_items = frappe._dict()
|
||||
|
||||
|
||||
select_fields = "item_code, sum(qty) as qty, rate" if doc.doctype=="Purchase Invoice" \
|
||||
else "item_code, sum(qty) as qty, rate, serial_no, batch_no"
|
||||
|
||||
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s
|
||||
|
||||
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s
|
||||
group by item_code""".format(select_fields, doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty, serial_no, batch_no from `tabPacked Item`
|
||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty, serial_no, batch_no from `tabPacked Item`
|
||||
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
|
||||
already_returned_items = get_already_returned_items(doc)
|
||||
|
||||
|
||||
items_returned = False
|
||||
for d in doc.get("items"):
|
||||
if flt(d.qty) < 0:
|
||||
@@ -77,16 +77,12 @@ def validate_returned_items(doc):
|
||||
ref = valid_items.get(d.item_code, frappe._dict())
|
||||
already_returned_qty = flt(already_returned_items.get(d.item_code))
|
||||
max_return_qty = flt(ref.qty) - already_returned_qty
|
||||
|
||||
|
||||
if already_returned_qty >= ref.qty:
|
||||
frappe.throw(_("Item {0} has already been returned").format(d.item_code), StockOverReturnError)
|
||||
elif abs(d.qty) > max_return_qty:
|
||||
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
|
||||
.format(d.idx, ref.qty, d.item_code), StockOverReturnError)
|
||||
elif ref.rate and (doc.doctype in ("Delivery Note", "Purchase Receipt") \
|
||||
or (doc.doctype=="Sales Invoice" and doc.update_stock==1)) and flt(d.rate) > ref.rate:
|
||||
frappe.throw(_("Row # {0}: Rate cannot be greater than {1} {2}")
|
||||
.format(d.idx, doc.doctype, doc.return_against))
|
||||
elif ref.batch_no and d.batch_no != ref.batch_no:
|
||||
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
|
||||
.format(d.idx, doc.doctype, doc.return_against))
|
||||
@@ -100,24 +96,24 @@ def validate_returned_items(doc):
|
||||
if s not in ref_serial_nos:
|
||||
frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}")
|
||||
.format(d.idx, s, doc.doctype, doc.return_against))
|
||||
|
||||
|
||||
items_returned = True
|
||||
|
||||
|
||||
if not items_returned:
|
||||
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
|
||||
|
||||
|
||||
def get_already_returned_items(doc):
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
select
|
||||
select
|
||||
child.item_code, sum(abs(child.qty)) as qty
|
||||
from
|
||||
`tab{0} Item` child, `tab{1}` par
|
||||
where
|
||||
from
|
||||
`tab{0} Item` child, `tab{1}` par
|
||||
where
|
||||
child.parent = par.name and par.docstatus = 1
|
||||
and ifnull(par.is_return, 0) = 1 and par.return_against = %s and child.qty < 0
|
||||
and ifnull(par.is_return, 0) = 1 and par.return_against = %s and child.qty < 0
|
||||
group by item_code
|
||||
""".format(doc.doctype, doc.doctype), doc.return_against))
|
||||
|
||||
|
||||
def make_return_doc(doctype, source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
def set_missing_values(source, target):
|
||||
@@ -127,13 +123,21 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
doc.ignore_pricing_rule = 1
|
||||
if doctype == "Sales Invoice":
|
||||
doc.is_pos = 0
|
||||
|
||||
|
||||
# look for Print Heading "Credit Note"
|
||||
if not doc.select_print_heading:
|
||||
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Credit Note"))
|
||||
|
||||
elif doctype == "Purchase Invoice":
|
||||
# look for Print Heading "Debit Note"
|
||||
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Debit Note"))
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
if tax.charge_type == "Actual":
|
||||
tax.tax_amount = -1 * tax.tax_amount
|
||||
|
||||
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1* source_doc.qty
|
||||
if doctype == "Purchase Receipt":
|
||||
@@ -151,16 +155,18 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.against_sales_invoice = source_doc.against_sales_invoice
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
target_doc.si_detail = source_doc.si_detail
|
||||
target_doc.expense_account = source_doc.expense_account
|
||||
elif doctype == "Sales Invoice":
|
||||
target_doc.sales_order = source_doc.sales_order
|
||||
target_doc.delivery_note = source_doc.delivery_note
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
target_doc.dn_detail = source_doc.dn_detail
|
||||
target_doc.expense_account = source_doc.expense_account
|
||||
|
||||
doclist = get_mapped_doc(doctype, source_name, {
|
||||
doctype: {
|
||||
"doctype": doctype,
|
||||
|
||||
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
|
||||
@@ -90,6 +90,10 @@ class StatusUpdater(Document):
|
||||
self.global_tolerance = None
|
||||
|
||||
for args in self.status_updater:
|
||||
if "target_ref_field" not in args:
|
||||
# if target_ref_field is not specified, the programmer does not want to validate qty / amount
|
||||
continue
|
||||
|
||||
# get unique transactions to update
|
||||
for d in self.get_all_children():
|
||||
if d.doctype == args['source_dt'] and d.get(args["join_field"]):
|
||||
@@ -140,8 +144,9 @@ class StatusUpdater(Document):
|
||||
.format(_(item["target_ref_field"].title()), item["reduce_by"]))
|
||||
|
||||
def update_qty(self, change_modified=True):
|
||||
"""
|
||||
Updates qty at row level
|
||||
"""Updates qty or amount at row level
|
||||
|
||||
:param change_modified: If true, updates `modified` and `modified_by` for target parent doc
|
||||
"""
|
||||
for args in self.status_updater:
|
||||
# condition to include current record (if submit or no if cancel)
|
||||
@@ -150,58 +155,74 @@ class StatusUpdater(Document):
|
||||
else:
|
||||
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
|
||||
|
||||
args['modified_cond'] = ''
|
||||
args['set_modified'] = ''
|
||||
if change_modified:
|
||||
args['modified_cond'] = ', modified = now()'
|
||||
args['set_modified'] = ', modified = now(), modified_by = "{0}"'\
|
||||
.format(frappe.db.escape(frappe.session.user))
|
||||
|
||||
# update quantities in child table
|
||||
for d in self.get_all_children():
|
||||
if d.doctype == args['source_dt']:
|
||||
# updates qty in the child table
|
||||
args['detail_id'] = d.get(args['join_field'])
|
||||
self._update_children(args)
|
||||
|
||||
args['second_source_condition'] = ""
|
||||
if args.get('second_source_dt') and args.get('second_source_field') \
|
||||
and args.get('second_join_field'):
|
||||
if not args.get("second_source_extra_cond"):
|
||||
args["second_source_extra_cond"] = ""
|
||||
if "percent_join_field" in args:
|
||||
self._update_percent_field(args)
|
||||
|
||||
args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s)
|
||||
from `tab%(second_source_dt)s`
|
||||
where `%(second_join_field)s`="%(detail_id)s"
|
||||
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s), 0) """ % args
|
||||
def _update_children(self, args):
|
||||
"""Update quantities or amount in child table"""
|
||||
for d in self.get_all_children():
|
||||
if d.doctype != args['source_dt']:
|
||||
continue
|
||||
|
||||
if args['detail_id']:
|
||||
if not args.get("extra_cond"): args["extra_cond"] = ""
|
||||
# updates qty in the child table
|
||||
args['detail_id'] = d.get(args['join_field'])
|
||||
|
||||
frappe.db.sql("""update `tab%(target_dt)s`
|
||||
set %(target_field)s = (select sum(%(source_field)s)
|
||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||
and (docstatus=1 %(cond)s) %(extra_cond)s) %(second_source_condition)s
|
||||
where name='%(detail_id)s'""" % args)
|
||||
args['second_source_condition'] = ""
|
||||
if args.get('second_source_dt') and args.get('second_source_field') \
|
||||
and args.get('second_join_field'):
|
||||
if not args.get("second_source_extra_cond"):
|
||||
args["second_source_extra_cond"] = ""
|
||||
|
||||
# get unique transactions to update
|
||||
for name in set([d.get(args['percent_join_field']) for d in self.get_all_children(args['source_dt'])]):
|
||||
if name:
|
||||
args['name'] = name
|
||||
args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s)
|
||||
from `tab%(second_source_dt)s`
|
||||
where `%(second_join_field)s`="%(detail_id)s"
|
||||
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s), 0) """ % args
|
||||
|
||||
# update percent complete in the parent table
|
||||
if args.get('target_parent_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
|
||||
ifnull(%(target_field)s, 0), %(target_field)s,
|
||||
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
|
||||
from `tab%(target_dt)s` where parent="%(name)s") %(modified_cond)s
|
||||
where name='%(name)s'""" % args)
|
||||
if args['detail_id']:
|
||||
if not args.get("extra_cond"): args["extra_cond"] = ""
|
||||
|
||||
# update field
|
||||
if args.get('status_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(status_field)s = if(ifnull(%(target_parent_field)s,0)<0.001,
|
||||
'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
|
||||
'Fully %(keyword)s', 'Partly %(keyword)s'))
|
||||
where name='%(name)s'""" % args)
|
||||
frappe.db.sql("""update `tab%(target_dt)s`
|
||||
set %(target_field)s = (select sum(%(source_field)s)
|
||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||
and (docstatus=1 %(cond)s) %(extra_cond)s) %(second_source_condition)s
|
||||
where name='%(detail_id)s'""" % args)
|
||||
|
||||
def _update_percent_field(self, args):
|
||||
"""Update percent field in parent transaction"""
|
||||
unique_transactions = set([d.get(args['percent_join_field']) for d in self.get_all_children(args['source_dt'])])
|
||||
|
||||
for name in unique_transactions:
|
||||
if not name:
|
||||
continue
|
||||
|
||||
args['name'] = name
|
||||
|
||||
# update percent complete in the parent table
|
||||
if args.get('target_parent_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
|
||||
ifnull(%(target_field)s, 0), %(target_field)s,
|
||||
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
|
||||
from `tab%(target_dt)s` where parent="%(name)s") %(set_modified)s
|
||||
where name='%(name)s'""" % args)
|
||||
|
||||
# update field
|
||||
if args.get('status_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(status_field)s = if(ifnull(%(target_parent_field)s,0)<0.001,
|
||||
'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
|
||||
'Fully %(keyword)s', 'Partly %(keyword)s'))
|
||||
where name='%(name)s'""" % args)
|
||||
|
||||
if args.get("set_modified"):
|
||||
frappe.get_doc(args["target_parent_dt"], name).notify_modified()
|
||||
|
||||
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
|
||||
ref_fieldname = ref_dt.lower().replace(" ", "_")
|
||||
|
||||
Reference in New Issue
Block a user