From fe13bfed441df49e82c71332c85dde158ea7b6bc Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 25 Aug 2015 12:49:40 +0530 Subject: [PATCH] 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 --- .../purchase_invoice/purchase_invoice.py | 2 +- .../purchase_invoice/purchase_invoice_list.js | 2 +- .../doctype/sales_invoice/sales_invoice.js | 1 + .../doctype/sales_invoice/sales_invoice.py | 30 +++-- .../sales_invoice/sales_invoice_list.js | 2 +- .../__init__.py | 0 .../credit_note___negative_invoice.json | 17 --- .../purchase_order_item.json | 23 +++- .../current/sales_purchase_return.md | 3 + .../controllers/sales_and_purchase_return.py | 76 ++++++------ erpnext/controllers/status_updater.py | 109 +++++++++++------- erpnext/patches.txt | 6 +- .../patches/v5_7/item_template_attributes.py | 3 - erpnext/patches/v5_8/__init__.py | 1 + .../v5_8/add_credit_note_print_heading.py | 14 +++ ...pdate_order_reference_in_return_entries.py | 73 +++++++----- erpnext/public/js/controllers/transaction.js | 17 +++ .../sales_order_item/sales_order_item.json | 23 +++- .../page/setup_wizard/install_fixtures.py | 12 +- .../doctype/delivery_note/delivery_note.py | 21 +++- .../delivery_note/delivery_note_list.js | 2 +- .../delivery_note_item.json | 6 +- .../purchase_receipt/purchase_receipt.py | 27 +++-- .../purchase_receipt/purchase_receipt_list.js | 2 +- erpnext/stock/doctype/serial_no/serial_no.py | 4 +- erpnext/stock/get_item_details.py | 4 + 26 files changed, 313 insertions(+), 167 deletions(-) delete mode 100644 erpnext/accounts/print_format/credit_note___negative_invoice/__init__.py delete mode 100644 erpnext/accounts/print_format/credit_note___negative_invoice/credit_note___negative_invoice.json create mode 100644 erpnext/change_log/current/sales_purchase_return.md create mode 100644 erpnext/patches/v5_8/__init__.py create mode 100644 erpnext/patches/v5_8/add_credit_note_print_heading.py rename erpnext/patches/{v5_7 => v5_8}/update_order_reference_in_return_entries.py (66%) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 132cb1027d..22089373b1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -127,7 +127,7 @@ class PurchaseInvoice(BuyingController): } }) - if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')): + if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')) and not self.is_return: self.validate_rate_with_reference_doc([ ["Purchase Order", "purchase_order", "po_detail"], ["Purchase Receipt", "purchase_receipt", "pr_detail"] diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index ccad5ce2ad..afcd61f228 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -7,7 +7,7 @@ frappe.listview_settings['Purchase Invoice'] = { "currency", "is_return"], get_indicator: function(doc) { if(cint(doc.is_return)==1) { - return [__("Return"), "darkgrey", "is_return,=,1"]; + return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { if(frappe.datetime.get_diff(doc.due_date) < 0) { return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 734684f69d..5e59078d75 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -64,6 +64,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte if(doc.outstanding_amount!=0 && !cint(doc.is_return)) { cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry).addClass("btn-primary"); } + } // Show buttons only when pos view is active diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 20c766aaa9..12740160de 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -82,11 +82,12 @@ class SalesInvoice(SellingController): self.check_prev_docstatus() if self.is_return: + # NOTE status updating bypassed for is_return self.status_updater = [] - + self.update_status_updater_args() self.update_prevdoc_status() - + if not self.is_return: self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.check_credit_limit() @@ -112,11 +113,12 @@ class SalesInvoice(SellingController): remove_against_link_from_jv(self.doctype, self.name) if self.is_return: + # NOTE status updating bypassed for is_return self.status_updater = [] - + self.update_status_updater_args() self.update_prevdoc_status() - + if not self.is_return: self.update_billing_status_for_zero_amount_refdoc("Sales Order") @@ -126,7 +128,7 @@ class SalesInvoice(SellingController): def update_status_updater_args(self): if cint(self.update_stock): - self.status_updater.append({ + self.status_updater.extend([{ 'source_dt':'Sales Invoice Item', 'target_dt':'Sales Order Item', 'target_parent_dt':'Sales Order', @@ -144,7 +146,21 @@ class SalesInvoice(SellingController): 'overflow_type': 'delivery', 'extra_cond': """ and exists(select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and ifnull(update_stock, 0) = 1)""" - }) + }, + { + 'source_dt': 'Sales Invoice Item', + 'target_dt': 'Sales Order Item', + 'join_field': 'so_detail', + 'target_field': 'returned_qty', + 'target_parent_dt': 'Sales Order', + # 'target_parent_field': 'per_delivered', + # 'target_ref_field': 'qty', + 'source_field': '-1 * qty', + # 'percent_join_field': 'sales_order', + # 'overflow_type': 'delivery', + 'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)""" + } + ]) def set_missing_values(self, for_validate=False): pos = self.set_pos_fields(for_validate) @@ -281,7 +297,7 @@ class SalesInvoice(SellingController): }, }) - if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')): + if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')) and not self.is_return: self.validate_rate_with_reference_doc([ ["Sales Order", "sales_order", "so_detail"], ["Delivery Note", "delivery_note", "dn_detail"] diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js index 11c9789195..55e8b211b9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js @@ -7,7 +7,7 @@ frappe.listview_settings['Sales Invoice'] = { "currency", "is_return"], get_indicator: function(doc) { if(cint(doc.is_return)==1) { - return [__("Return"), "darkgrey", "is_return,=,1"]; + return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if(flt(doc.outstanding_amount)==0) { return [__("Paid"), "green", "outstanding_amount,=,0"] } else if (flt(doc.outstanding_amount) > 0 && doc.due_date > frappe.datetime.get_today()) { diff --git a/erpnext/accounts/print_format/credit_note___negative_invoice/__init__.py b/erpnext/accounts/print_format/credit_note___negative_invoice/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/accounts/print_format/credit_note___negative_invoice/credit_note___negative_invoice.json b/erpnext/accounts/print_format/credit_note___negative_invoice/credit_note___negative_invoice.json deleted file mode 100644 index e7d7eabaff..0000000000 --- a/erpnext/accounts/print_format/credit_note___negative_invoice/credit_note___negative_invoice.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "creation": "2015-07-22 17:45:22.220567", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sales Invoice", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "\n\n

\n\t{{ doc.company }}
\n\t{{ doc.select_print_heading or _(\"Credit Note\") }}
\n

\n\n
\n\n{%- for label, value in (\n (_(\"Receipt No\"), doc.name),\n (_(\"Date\"), doc.get_formatted(\"posting_date\")),\n\t(_(\"Customer\"), doc.customer_name),\n (_(\"Amount\"), \"\" + doc.get_formatted(\"grand_total\", absolute_value=True) + \"
\" + (doc.in_words or \"\")),\n\t(_(\"Against\"), doc.return_against),\n (_(\"Remarks\"), doc.remarks)\n) -%}\n\n\t\t
\n\t\t
\n\t\t
{{ value }}
\n\t\t
\n{%- endfor -%}\n\n
\n
\n

\n {{ _(\"For\") }} {{ doc.company }},
\n
\n
\n
\n {{ _(\"Authorized Signatory\") }}\n

", - "modified": "2015-07-22 17:45:22.220567", - "modified_by": "Administrator", - "name": "Credit Note - Negative Invoice", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Server", - "standard": "Yes" -} \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index bd862852cd..7faa2f388a 100755 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -1002,6 +1002,27 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "depends_on": "returned_qty", + "fieldname": "returned_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Returned Qty", + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "fieldname": "billed_amt", @@ -1074,7 +1095,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-08-19 12:46:32.384796", + "modified": "2015-08-25 06:42:00.898647", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/change_log/current/sales_purchase_return.md b/erpnext/change_log/current/sales_purchase_return.md new file mode 100644 index 0000000000..0152d57855 --- /dev/null +++ b/erpnext/change_log/current/sales_purchase_return.md @@ -0,0 +1,3 @@ +- Fixes in Sales and Purchase Return + - Update Delivered, Ordered and Returned Quantity based on Return transaction + - Allow different Rate in Return transaction diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index bb3b63cdd3..f4895ca649 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -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], } diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index c7d24396af..9c67e9a1b8 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -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(" ", "_") diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ea10054d52..80518410cb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -191,6 +191,10 @@ erpnext.patches.v5_4.stock_entry_additional_costs erpnext.patches.v5_4.cleanup_journal_entry #2015-08-14 execute:frappe.db.sql("update `tabProduction Order` pro set description = (select description from tabItem where name=pro.production_item) where ifnull(description, '') = ''") erpnext.patches.v5_7.item_template_attributes +execute:frappe.delete_doc_if_exists("DocType", "Manage Variants") +execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item") erpnext.patches.v4_2.repost_reserved_qty #2015-08-20 erpnext.patches.v5_4.update_purchase_cost_against_project -erpnext.patches.v5_7.update_order_reference_in_return_entries \ No newline at end of file +erpnext.patches.v5_8.update_order_reference_in_return_entries +erpnext.patches.v5_8.add_credit_note_print_heading +execute:frappe.delete_doc_if_exists("Print Format", "Credit Note - Negative Invoice") diff --git a/erpnext/patches/v5_7/item_template_attributes.py b/erpnext/patches/v5_7/item_template_attributes.py index e93bfe45a4..c0e68516cf 100644 --- a/erpnext/patches/v5_7/item_template_attributes.py +++ b/erpnext/patches/v5_7/item_template_attributes.py @@ -55,9 +55,6 @@ def migrate_manage_variants(): template.set('attributes', attributes) template.save() - frappe.delete_doc("DocType", "Manage Variants") - frappe.delete_doc("DocType", "Manage Variants Item") - # patch old style def migrate_item_variants(): for item in frappe.get_all("Item", filters={"has_variants": 1}): diff --git a/erpnext/patches/v5_8/__init__.py b/erpnext/patches/v5_8/__init__.py new file mode 100644 index 0000000000..baffc48825 --- /dev/null +++ b/erpnext/patches/v5_8/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/erpnext/patches/v5_8/add_credit_note_print_heading.py b/erpnext/patches/v5_8/add_credit_note_print_heading.py new file mode 100644 index 0000000000..476cbc8956 --- /dev/null +++ b/erpnext/patches/v5_8/add_credit_note_print_heading.py @@ -0,0 +1,14 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ + +def execute(): + for print_heading in (_("Credit Note"), _("Debit Note")): + if not frappe.db.exists("Print Heading", print_heading): + frappe.get_doc({ + "doctype": "Print Heading", + "print_heading": print_heading + }).insert() diff --git a/erpnext/patches/v5_7/update_order_reference_in_return_entries.py b/erpnext/patches/v5_8/update_order_reference_in_return_entries.py similarity index 66% rename from erpnext/patches/v5_7/update_order_reference_in_return_entries.py rename to erpnext/patches/v5_8/update_order_reference_in_return_entries.py index c957242260..c6cfceb6da 100644 --- a/erpnext/patches/v5_7/update_order_reference_in_return_entries.py +++ b/erpnext/patches/v5_8/update_order_reference_in_return_entries.py @@ -7,43 +7,50 @@ import frappe def execute(): # sales return return_entries = list(frappe.db.sql(""" - select dn.name as name, dn_item.name as row_id, dn.return_against, + select dn.name as name, dn_item.name as row_id, dn.return_against, dn_item.item_code, "Delivery Note" as doctype from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn where dn_item.parent=dn.name and dn.is_return=1 and dn.docstatus < 2 """, as_dict=1)) - + return_entries += list(frappe.db.sql(""" - select si.name as name, si_item.name as row_id, si.return_against, - si_item.item_code, "Sales Invoice" as doctype + select si.name as name, si_item.name as row_id, si.return_against, + si_item.item_code, "Sales Invoice" as doctype, update_stock from `tabSales Invoice Item` si_item, `tabSales Invoice` si - where si_item.parent=si.name and si.is_return=1 and si.update_stock=1 and si.docstatus < 2 + where si_item.parent=si.name and si.is_return=1 and si.docstatus < 2 """, as_dict=1)) - + for d in return_entries: ref_field = "against_sales_order" if d.doctype == "Delivery Note" else "sales_order" order_details = frappe.db.sql(""" - select {0} as sales_order, so_detail - from `tab{1} Item` item - where - parent=%s and item_code=%s + select {ref_field} as sales_order, so_detail, + (select transaction_date from `tabSales Order` where name=item.{ref_field}) as sales_order_date + from `tab{doctype} Item` item + where + parent=%s + and item_code=%s and ifnull(so_detail, '') !='' - order by - (select transaction_date from `tabSales Order` where name=item.{3}) DESC - """.format(ref_field, d.doctype, ref_field, ref_field), (d.return_against, d.item_code), as_dict=1) - + order by sales_order_date DESC limit 1 + """.format(ref_field=ref_field, doctype=d.doctype), (d.return_against, d.item_code), as_dict=1) + if order_details: frappe.db.sql(""" - update `tab{0} Item` - set {1}=%s, so_detail=%s + update `tab{doctype} Item` + set {ref_field}=%s, so_detail=%s where name=%s - """.format(d.doctype, ref_field), + """.format(doctype=d.doctype, ref_field=ref_field), (order_details[0].sales_order, order_details[0].so_detail, d.row_id)) - - doc = frappe.get_doc(d.doctype, d.name) - doc.update_reserved_qty() - - + + if (d.doctype=="Sales Invoice" and d.update_stock) or d.doctype=="Delivery Note": + doc = frappe.get_doc(d.doctype, d.name) + doc.update_reserved_qty() + + if d.doctype=="Sales Invoice": + doc.status_updater = [] + doc.update_status_updater_args() + + doc.update_prevdoc_status() + #-------------------------- # purchase return return_entries = frappe.db.sql(""" @@ -51,26 +58,28 @@ def execute(): from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr where pr_item.parent=pr.name and pr.is_return=1 and pr.docstatus < 2 """, as_dict=1) - + for d in return_entries: order_details = frappe.db.sql(""" - select prevdoc_docname as purchase_order, prevdoc_detail_docname as po_detail + select prevdoc_docname as purchase_order, prevdoc_detail_docname as po_detail, + (select transaction_date from `tabPurchase Order` where name=item.prevdoc_detail_docname) as purchase_order_date from `tabPurchase Receipt Item` item - where - parent=%s and item_code=%s - and ifnull(prevdoc_detail_docname, '') !='' + where + parent=%s + and item_code=%s + and ifnull(prevdoc_detail_docname, '') !='' and ifnull(prevdoc_doctype, '') = 'Purchase Order' and ifnull(prevdoc_detail_docname, '') != '' - order by - (select transaction_date from `tabPurchase Order` where name=item.prevdoc_detail_docname) DESC + order by purchase_order_date DESC limit 1 """, (d.return_against, d.item_code), as_dict=1) - + if order_details: frappe.db.sql(""" update `tabPurchase Receipt Item` set prevdoc_doctype='Purchase Order', prevdoc_docname=%s, prevdoc_detail_docname=%s where name=%s """, (order_details[0].purchase_order, order_details[0].po_detail, d.row_id)) - + pr = frappe.get_doc("Purchase Receipt", d.name) pr.update_ordered_qty() - \ No newline at end of file + pr.update_prevdoc_status() + diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index ba10702bd9..66794c90af 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -88,6 +88,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.set_dynamic_labels(); erpnext.pos.make_pos_btn(this.frm); this.setup_sms(); + this.make_show_payments_btn(); }, apply_default_taxes: function() { @@ -123,6 +124,22 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var sms_man = new SMSManager(this.frm.doc); }, + make_show_payments_btn: function() { + var me = this; + if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doctype)) { + if(this.frm.doc.outstanding_amount !== this.frm.doc.base_grand_total) { + this.frm.add_custom_button(__("Show Payments"), function() { + frappe.route_options = { + "Journal Entry Account.reference_type": me.frm.doc.doctype, + "Journal Entry Account.reference_name": me.frm.doc.name + }; + + frappe.set_route("List", "Journal Entry"); + }); + } + } + }, + hide_currency_and_price_list: function() { if(this.frm.doc.conversion_rate == 1 && this.frm.doc.docstatus > 0) { hide_field("currency_and_price_list"); diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 0d0d50dc33..af80617824 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -840,6 +840,27 @@ "unique": 0, "width": "100px" }, + { + "allow_on_submit": 0, + "depends_on": "returned_qty", + "fieldname": "returned_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Returned Qty", + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "fieldname": "billed_amt", @@ -960,7 +981,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-08-19 12:46:32.930498", + "modified": "2015-08-25 06:42:11.062909", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/setup/page/setup_wizard/install_fixtures.py b/erpnext/setup/page/setup_wizard/install_fixtures.py index dd3ec3a2e6..6ecd9ffa77 100644 --- a/erpnext/setup/page/setup_wizard/install_fixtures.py +++ b/erpnext/setup/page/setup_wizard/install_fixtures.py @@ -41,15 +41,15 @@ def install(country=None): {'doctype': 'Expense Claim Type', 'name': _('Travel'), 'expense_type': _('Travel')}, # leave type - {'doctype': 'Leave Type', 'leave_type_name': _('Casual Leave'), 'name': _('Casual Leave'), + {'doctype': 'Leave Type', 'leave_type_name': _('Casual Leave'), 'name': _('Casual Leave'), 'is_encash': 1, 'is_carry_forward': 1, 'max_days_allowed': '3', 'include_holiday': 1}, - {'doctype': 'Leave Type', 'leave_type_name': _('Compensatory Off'), 'name': _('Compensatory Off'), + {'doctype': 'Leave Type', 'leave_type_name': _('Compensatory Off'), 'name': _('Compensatory Off'), 'is_encash': 0, 'is_carry_forward': 0, 'include_holiday': 1}, - {'doctype': 'Leave Type', 'leave_type_name': _('Sick Leave'), 'name': _('Sick Leave'), + {'doctype': 'Leave Type', 'leave_type_name': _('Sick Leave'), 'name': _('Sick Leave'), 'is_encash': 0, 'is_carry_forward': 0, 'include_holiday': 1}, - {'doctype': 'Leave Type', 'leave_type_name': _('Privilege Leave'), 'name': _('Privilege Leave'), + {'doctype': 'Leave Type', 'leave_type_name': _('Privilege Leave'), 'name': _('Privilege Leave'), 'is_encash': 0, 'is_carry_forward': 0, 'include_holiday': 1}, - {'doctype': 'Leave Type', 'leave_type_name': _('Leave Without Pay'), 'name': _('Leave Without Pay'), + {'doctype': 'Leave Type', 'leave_type_name': _('Leave Without Pay'), 'name': _('Leave Without Pay'), 'is_encash': 0, 'is_carry_forward': 0, 'is_lwp':1, 'include_holiday': 1}, # Employment Type @@ -173,6 +173,8 @@ def install(country=None): {"doctype": "Offer Term", "offer_term": _("Notice Period")}, {"doctype": "Offer Term", "offer_term": _("Incentives")}, + {'doctype': "Print Heading", 'print_heading': _("Credit Note")}, + {'doctype': "Print Heading", 'print_heading': _("Debit Note")} ] from erpnext.setup.page.setup_wizard.fixtures import industry_type diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 201546e457..1ac1cf47fe 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -47,6 +47,19 @@ class DeliveryNote(SellingController): 'source_field': 'qty', 'percent_join_field': 'against_sales_invoice', 'overflow_type': 'delivery' + }, + { + 'source_dt': 'Delivery Note Item', + 'target_dt': 'Sales Order Item', + 'join_field': 'so_detail', + 'target_field': 'returned_qty', + 'target_parent_dt': 'Sales Order', + # 'target_parent_field': 'per_delivered', + # 'target_ref_field': 'qty', + 'source_field': '-1 * qty', + # 'percent_join_field': 'against_sales_order', + # 'overflow_type': 'delivery', + 'extra_cond': """ and exists (select name from `tabDelivery Note` where name=`tabDelivery Note Item`.parent and is_return=1)""" }] def onload(self): @@ -118,7 +131,7 @@ class DeliveryNote(SellingController): }, }) - if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')): + if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')) and not self.is_return: self.validate_rate_with_reference_doc([["Sales Order", "sales_order", "so_detail"], ["Sales Invoice", "sales_invoice", "si_detail"]]) @@ -176,7 +189,7 @@ class DeliveryNote(SellingController): # update delivered qty in sales order self.update_prevdoc_status() - + if not self.is_return: self.check_credit_limit() @@ -354,8 +367,8 @@ def make_packing_slip(source_name, target_doc=None): return doclist - + @frappe.whitelist() def make_sales_return(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc - return make_return_doc("Delivery Note", source_name, target_doc) \ No newline at end of file + return make_return_doc("Delivery Note", source_name, target_doc) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index dce6863441..d79015e566 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -3,7 +3,7 @@ frappe.listview_settings['Delivery Note'] = { "transporter_name", "grand_total", "is_return"], get_indicator: function(doc) { if(cint(doc.is_return)==1) { - return [__("Return"), "darkgrey", "is_return,=,1"]; + return [__("Return"), "darkgrey", "is_return,=,Yes"]; } } }; diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 18138c003a..e764b6ab9b 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -912,7 +912,7 @@ "in_filter": 0, "in_list_view": 1, "label": "Against Sales Order", - "no_copy": 0, + "no_copy": 1, "options": "Sales Order", "permlevel": 0, "print_hide": 1, @@ -932,7 +932,7 @@ "in_filter": 0, "in_list_view": 0, "label": "Against Sales Invoice", - "no_copy": 0, + "no_copy": 1, "options": "Sales Invoice", "permlevel": 0, "print_hide": 1, @@ -1039,7 +1039,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-08-19 12:46:31.447022", + "modified": "2015-08-25 07:15:19.811365", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index f4273b27ed..58f165c040 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -29,6 +29,19 @@ class PurchaseReceipt(BuyingController): 'source_field': 'qty', 'percent_join_field': 'prevdoc_docname', 'overflow_type': 'receipt' + }, + { + 'source_dt': 'Purchase Receipt Item', + 'target_dt': 'Purchase Order Item', + 'join_field': 'prevdoc_detail_docname', + 'target_field': 'returned_qty', + 'target_parent_dt': 'Purchase Order', + # 'target_parent_field': 'per_received', + # 'target_ref_field': 'qty', + 'source_field': '-1 * qty', + # 'percent_join_field': 'prevdoc_docname', + # 'overflow_type': 'receipt', + 'extra_cond': """ and exists (select name from `tabPurchase Receipt` where name=`tabPurchase Receipt Item`.parent and is_return=1)""" }] def onload(self): @@ -68,12 +81,12 @@ class PurchaseReceipt(BuyingController): from `tabLanded Cost Item` where docstatus = 1 and purchase_receipt_item = %s""", d.name) d.landed_cost_voucher_amount = lc_voucher_amount[0][0] if lc_voucher_amount else 0.0 - + def validate_purchase_return(self): for d in self.get("items"): if self.is_return and flt(d.rejected_qty) != 0: frappe.throw(_("Row #{0}: Rejected Qty can not be entered in Purchase Return").format(d.idx)) - + # validate rate with ref PR def validate_rejected_warehouse(self): @@ -113,7 +126,7 @@ class PurchaseReceipt(BuyingController): } }) - if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')): + if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')) and not self.is_return: self.validate_rate_with_reference_doc([["Purchase Order", "prevdoc_docname", "prevdoc_detail_docname"]]) def po_required(self): @@ -223,7 +236,7 @@ class PurchaseReceipt(BuyingController): self.update_prevdoc_status() self.update_ordered_qty() - + if not self.is_return: purchase_controller.update_last_purchase_rate(self, 1) @@ -261,7 +274,7 @@ class PurchaseReceipt(BuyingController): self.update_prevdoc_status() # Must be called after updating received qty in PO self.update_ordered_qty() - + if not self.is_return: pc_obj.update_last_purchase_rate(self, 0) @@ -291,7 +304,7 @@ class PurchaseReceipt(BuyingController): if warehouse_account.get(d.warehouse): val_rate_db_precision = 6 if cint(d.precision("valuation_rate")) <= 6 else 9 - + # warehouse account gl_entries.append(self.get_gl_dict({ "account": warehouse_account[d.warehouse], @@ -466,4 +479,4 @@ def get_invoiced_qty_map(purchase_receipt): @frappe.whitelist() def make_purchase_return(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc - return make_return_doc("Purchase Receipt", source_name, target_doc) \ No newline at end of file + return make_return_doc("Purchase Receipt", source_name, target_doc) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js index 64d5456bfe..838fbb03a1 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js @@ -3,7 +3,7 @@ frappe.listview_settings['Purchase Receipt'] = { "transporter_name", "is_return"], get_indicator: function(doc) { if(cint(doc.is_return)==1) { - return [__("Return"), "darkgrey", "is_return,=,1"]; + return [__("Return"), "darkgrey", "is_return,=,Yes"]; } } }; diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index fb15195291..0df0ba2a52 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -33,7 +33,7 @@ class SerialNo(StockController): self.validate_warehouse() self.validate_item() self.on_stock_ledger_entry() - + def set_maintenance_status(self): if not self.warranty_expiry_date and not self.amc_expiry_date: self.maintenance_status = None @@ -209,7 +209,7 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty)) if len(serial_nos) and len(serial_nos) != abs(cint(sle.actual_qty)): - frappe.throw(_("{0} Serial Numbers required for Item {0}. Only {0} provided.").format(sle.actual_qty, sle.item_code, len(serial_nos)), + frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.").format(sle.actual_qty, sle.item_code, len(serial_nos)), SerialNoQtyError) if len(serial_nos) != len(set(serial_nos)): diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index d08c63c255..0cec77b4ef 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -257,6 +257,10 @@ def validate_price_list(args): def validate_conversion_rate(args, meta): from erpnext.controllers.accounts_controller import validate_conversion_rate + if (not args.conversion_rate + and args.currency==frappe.db.get_value("Company", args.company, "default_currency")): + args.conversion_rate = 1.0 + # validate currency conversion rate validate_conversion_rate(args.currency, args.conversion_rate, meta.get_label("conversion_rate"), args.company)