From 524ebbcbb9071c2b41b5ae072ee3102af2850cc3 Mon Sep 17 00:00:00 2001 From: jof2jc Date: Mon, 8 Feb 2016 23:44:55 +0700 Subject: [PATCH 01/15] Added Make Receipt (Update Stock) and IsCash option on Purchase Invoice --- .../purchase_invoice/purchase_invoice.js | 135 ++++++++++- .../purchase_invoice/purchase_invoice.json | 207 ++++++++++++++-- .../purchase_invoice/purchase_invoice.py | 167 ++++++++++++- .../purchase_invoice_item.json | 220 ++++++++++++++++++ .../controllers/sales_and_purchase_return.py | 1 + erpnext/controllers/stock_controller.py | 2 +- .../public/js/controllers/taxes_and_totals.js | 4 +- 7 files changed, 715 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 32ad6d2820..7ac5400a64 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -4,6 +4,7 @@ frappe.provide("erpnext.accounts"); {% include 'buying/doctype/purchase_common/purchase_common.js' %}; + erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ onload: function() { this._super(); @@ -14,6 +15,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ this.frm.set_df_property("credit_to", "print_hide", 0); } } + + cur_frm.cscript.hide_fields(this.frm.doc); }, refresh: function(doc) { @@ -28,9 +31,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ this.frm.add_custom_button(__('Payment'), this.make_bank_entry, __("Make")); cur_frm.page.set_inner_btn_group_as_primary(__("Make")); } - if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { - cur_frm.add_custom_button(__('Debit Note'), this.make_debit_note, __("Make")); + + if (!doc.make_receipt) { + if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { + cur_frm.add_custom_button(__('Debit Note'), this.make_debit_note, __("Make")); + } } + else { + cur_frm.add_custom_button(__('Return'), this.make_debit_note, __("Make")); + } + } if(doc.docstatus===0) { @@ -62,6 +72,52 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }, __("Get items from")); } } + if(doc.docstatus==1 && doc.make_receipt==1) { + this.show_stock_ledger(); + } + + }, + + received_qty: function(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["qty", "received_qty"]); + + item.qty = (item.qty < item.received_qty) ? item.qty : item.received_qty; + this.qty(doc, cdt, cdn); + }, + + qty: function(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["qty", "received_qty"]); + + if(!(item.received_qty || item.rejected_qty) && item.qty) { + item.received_qty = item.qty; + } + + if(item.qty > item.received_qty) { + msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "qty", item.name)), + __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])) + item.qty = item.rejected_qty = 0.0; + } else { + item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); + } + + this._super(doc, cdt, cdn); + }, + + rejected_qty: function(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]); + + if(item.rejected_qty > item.received_qty) { + msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "rejected_qty", item.name)), + __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])); + item.qty = item.rejected_qty = 0.0; + } else { + item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); + } + + this.qty(doc, cdt, cdn); }, supplier: function() { @@ -100,12 +156,30 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ } }, + is_cash: function() { + cur_frm.cscript.hide_fields(this.frm.doc); + if(cint(this.frm.doc.is_cash)) { + if(!this.frm.doc.company) { + this.frm.set_value("is_cash", 0); + msgprint(__("Please specify Company to proceed")); + } + } + this.calculate_outstanding_amount(); + this.frm.refresh_fields(); + }, + write_off_amount: function() { this.set_in_company_currency(this.frm.doc, ["write_off_amount"]); this.calculate_outstanding_amount(); this.frm.refresh_fields(); }, + paid_amount: function() { + this.set_in_company_currency(this.frm.doc, ["paid_amount"]); + this.write_off_outstanding_amount(); + this.frm.refresh_fields(); + }, + allocated_amount: function() { this.calculate_total_advance(); this.frm.refresh_fields(); @@ -137,6 +211,63 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); +// Hide Fields +// ------------ +cur_frm.cscript.hide_fields = function(doc) { + par_flds = ['due_date', 'is_opening', 'advances_section', 'from_date', 'to_date']; + + if(cint(doc.is_cash) == 1) { + hide_field(par_flds); + } else { + for (i in par_flds) { + var docfield = frappe.meta.docfield_map[doc.doctype][par_flds[i]]; + if(!docfield.hidden) unhide_field(par_flds[i]); + } + + } + + item_flds_stock = ['sc_wh', 'received_qty', 'rejected_qty']; + + //item_flds_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse'] + cur_frm.fields_dict['items'].grid.set_column_disp(item_flds_stock, + (cint(doc.make_receipt)==1 ? true : false)); + + cur_frm.refresh_fields(); +} + +cur_frm.cscript.make_receipt = function(doc, dt, dn) { + cur_frm.cscript.hide_fields(doc, dt, dn); +} + +cur_frm.cscript.mode_of_payment = function(doc) { + if(doc.is_cash) { + return cur_frm.call({ + method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", + args: { + "mode_of_payment": doc.mode_of_payment, + "company": doc.company + }, + callback: function(r, rt) { + if(r.message) { + cur_frm.set_value("cash_bank_account", r.message["account"]); + } + + } + }); + } +} + +cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { + return { + filters: [ + ["Account", "account_type", "in", ["Cash", "Bank"]], + ["Account", "root_type", "=", "Asset"], + ["Account", "is_group", "=",0], + ["Account", "company", "=", doc.company] + ] + } +} + cur_frm.cscript.make_bank_entry = function() { return frappe.call({ method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index bba4a4aeb9..df47ba7656 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -803,6 +803,31 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "0", + "fieldname": "make_receipt", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Make Receipt (Update Stock)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1663,6 +1688,156 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "collapsible_depends_on": "paid_amount", + "depends_on": "eval:doc.is_cash===1", + "fieldname": "sc_br_payments", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Payments", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Mode of Payment", + "length": 0, + "no_copy": 0, + "oldfieldname": "mode_of_payment", + "oldfieldtype": "Select", + "options": "Mode of Payment", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "cash_bank_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Cash/Bank Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "col_br_payments", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "paid_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Paid Amount", + "length": 0, + "no_copy": 0, + "options": "currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Paid Amount (Company Currency)", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -2700,18 +2875,19 @@ "permissions": [ { "amend": 1, - "apply_user_permissions": 0, + "apply_user_permissions": 1, "cancel": 1, "create": 1, "delete": 0, - "email": 1, - "export": 0, + "email": 0, + "export": 1, "if_owner": 0, "import": 0, "permlevel": 0, "print": 1, "read": 1, - "report": 1, + "report": 0, + "restrict": 0, "role": "Accounts User", "set_user_permissions": 0, "share": 1, @@ -2720,18 +2896,19 @@ }, { "amend": 0, - "apply_user_permissions": 0, + "apply_user_permissions": 1, "cancel": 0, "create": 0, "delete": 0, - "email": 1, + "email": 0, "export": 0, "if_owner": 0, "import": 0, "permlevel": 0, - "print": 1, + "print": 0, "read": 1, - "report": 1, + "report": 0, + "restrict": 0, "role": "Purchase User", "set_user_permissions": 0, "share": 0, @@ -2743,15 +2920,16 @@ "apply_user_permissions": 0, "cancel": 1, "create": 1, - "delete": 1, - "email": 1, - "export": 0, + "delete": 0, + "email": 0, + "export": 1, "if_owner": 0, "import": 0, "permlevel": 0, "print": 1, "read": 1, "report": 1, + "restrict": 0, "role": "Accounts Manager", "set_user_permissions": 0, "share": 1, @@ -2760,7 +2938,7 @@ }, { "amend": 0, - "apply_user_permissions": 0, + "apply_user_permissions": 1, "cancel": 0, "create": 0, "delete": 0, @@ -2772,6 +2950,7 @@ "print": 1, "read": 1, "report": 1, + "restrict": 0, "role": "Auditor", "set_user_permissions": 0, "share": 0, @@ -2792,11 +2971,11 @@ "print": 0, "read": 1, "report": 0, - "role": "Accounts Manager", + "role": "All", "set_user_permissions": 0, "share": 0, "submit": 0, - "write": 1 + "write": 0 } ], "read_only": 0, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 427e04098d..ba80bcfbb6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, formatdate, flt, getdate +from frappe.utils import cstr, cint, formatdate, flt, getdate from frappe import msgprint, _, throw from erpnext.setup.utils import get_company_currency import frappe.defaults @@ -13,6 +13,7 @@ from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po + form_grid_templates = { "items": "templates/form_grid/item_grid.html" } @@ -45,6 +46,23 @@ class PurchaseInvoice(BuyingController): self.validate_supplier_invoice() self.validate_advance_jv("Purchase Order") + # validate cash purchase + if (self.is_cash == 1): + self.validate_cash() + + # validate stock items + if (self.make_receipt == 1): + self.validate_purchase_return() + self.validate_rejected_warehouse() + self.validate_accepted_rejected_qty() + + # sub-contracting + # self.validate_for_subcontracting() + # self.create_raw_materials_supplied("supplied_items") + # self.set_landed_cost_voucher_amount() + # self.update_valuation_rate("items") + + self.check_active_purchase_items() self.check_conversion_rate() self.validate_credit_to_acc() self.clear_unallocated_advances("Purchase Invoice Advance", "advances") @@ -58,6 +76,45 @@ class PurchaseInvoice(BuyingController): self.validate_fixed_asset_account() self.create_remarks() + def validate_cash(self): + if not self.cash_bank_account and flt(self.paid_amount): + frappe.throw(_("Cash or Bank Account is mandatory for making payment entry")) + + if flt(self.paid_amount) + flt(self.write_off_amount) \ + - flt(self.base_grand_total) > 1/(10**(self.precision("base_grand_total") + 1)): + frappe.throw(_("""Paid amount + Write Off Amount can not be greater than Grand Total""")) + + 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): + for d in self.get("items"): + if flt(d.rejected_qty) and not d.rejected_warehouse: + d.rejected_warehouse = self.rejected_warehouse + if not d.rejected_warehouse: + frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code)) + + # validate accepted and rejected qty + def validate_accepted_rejected_qty(self): + for d in self.get("items"): + if not flt(d.received_qty) and flt(d.qty): + d.received_qty = flt(d.qty) - flt(d.rejected_qty) + + elif not flt(d.qty) and flt(d.rejected_qty): + d.qty = flt(d.received_qty) - flt(d.rejected_qty) + + elif not flt(d.rejected_qty): + d.rejected_qty = flt(d.received_qty) - flt(d.qty) + + # Check Received Qty = Accepted Qty + Rejected Qty + if ((flt(d.qty) + flt(d.rejected_qty)) != flt(d.received_qty)): + frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) + + def create_remarks(self): if not self.remarks: if self.bill_no and self.bill_date: @@ -228,6 +285,75 @@ class PurchaseInvoice(BuyingController): from erpnext.accounts.utils import reconcile_against_document reconcile_against_document(lst) + def update_status_updater_args(self): + if cint(self.make_receipt): + self.status_updater.extend([{ + 'source_dt': 'Purchase Invoice Item', + 'target_dt': 'Purchase Order Item', + 'join_field': 'po_detail', + 'target_field': 'received_qty', + 'target_parent_dt': 'Purchase Order', + 'target_parent_field': 'per_received', + 'target_ref_field': 'qty', + 'source_field': 'qty', + 'percent_join_field':'purchase_order', + # 'percent_join_field': 'prevdoc_docname', + 'overflow_type': 'receipt', + 'extra_cond': """ and exists(select name from `tabPurchase Invoice` + where name=`tabPurchase Invoice Item`.parent and make_receipt = 1)""" + }, + { + 'source_dt': 'Purchase Invoice Item', + 'target_dt': 'Purchase Order Item', + 'join_field': 'po_detail', + '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 Invoice` where name=`tabPurchase Invoice Item`.parent and is_return=1)""" + } + ]) + + def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): + sl_entries = [] + stock_items = self.get_stock_items() + + for d in self.get('items'): + if d.item_code in stock_items and d.warehouse: + pr_qty = flt(d.qty) * flt(d.conversion_factor) + + if pr_qty: + val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9 + rate = flt(d.valuation_rate, val_rate_db_precision) + sle = self.get_sl_entries(d, { + "actual_qty": flt(pr_qty), + "serial_no": cstr(d.serial_no).strip() + }) + if self.is_return: + sle.update({ + "outgoing_rate": rate + }) + else: + sle.update({ + "incoming_rate": rate + }) + sl_entries.append(sle) + + if flt(d.rejected_qty) > 0: + sl_entries.append(self.get_sl_entries(d, { + "warehouse": d.rejected_warehouse, + "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), + "serial_no": cstr(d.rejected_serial_no).strip(), + "incoming_rate": 0.0 + })) + + # self.bk_flush_supp_wh(sl_entries) + self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, + via_landed_cost_voucher=via_landed_cost_voucher) + def on_submit(self): self.check_prev_docstatus() self.validate_asset() @@ -235,8 +361,18 @@ class PurchaseInvoice(BuyingController): frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) + # make purchase receipt + if (self.make_receipt == 1): + # from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_stock_ledger + self.update_stock_ledger() + self.make_gl_entries() + from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit + update_serial_nos_after_submit(self, "items") + self.update_status_updater_args() + self.update_prevdoc_status() + # this sequence because outstanding may get -negative - self.make_gl_entries() + self.make_gl_entries1() if not self.is_return: self.update_against_document_in_jv() self.update_prevdoc_status() @@ -281,6 +417,33 @@ class PurchaseInvoice(BuyingController): gl_entries = [] + # Make Cash GL Entries + if cint(self.is_cash) and self.cash_bank_account and self.paid_amount: + bank_account_currency = get_account_currency(self.cash_bank_account) + # CASH, make payment entries + gl_entries.append( + self.get_gl_dict({ + "account": self.credit_to, + "party_type": "Supplier", + "party": self.supplier, + "against": self.cash_bank_account, + "debit": self.base_paid_amount, + "debit_in_account_currency": self.base_paid_amount \ + if self.party_account_currency==self.company_currency else self.paid_amount, + "against_voucher": self.return_against if cint(self.is_return) else self.name, + "against_voucher_type": self.doctype, + }, self.party_account_currency) + ) + gl_entries.append( + self.get_gl_dict({ + "account": self.cash_bank_account, + "against": self.supplier, + "credit": self.base_paid_amount, + "credit_in_account_currency": self.base_paid_amount \ + if bank_account_currency==self.company_currency else self.paid_amount + }, bank_account_currency) + ) + # parent's gl entry if self.grand_total: # Didnot use base_grand_total to book rounding loss gle diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 784c7478d8..cc9e1d3f56 100755 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -213,6 +213,30 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "received_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Received Qty", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "2", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 1, @@ -239,6 +263,30 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "rejected_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Rejected Qty", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "2", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -763,6 +811,178 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "sc_wh", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Warehouse", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "", + "fieldname": "warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Accepted Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "rejected_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Rejected Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "batch_no", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Batch No", + "length": 0, + "no_copy": 0, + "options": "Batch", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "col_br_wh", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "serial_no", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Serial No", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "rejected_serial_no", + "fieldtype": "Text", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Rejected Serial No", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index ac8a5dfd70..91877068e7 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -152,6 +152,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.prevdoc_docname = source_doc.prevdoc_docname target_doc.prevdoc_detail_docname = source_doc.prevdoc_detail_docname elif doctype == "Purchase Invoice": + target_doc.received_qty = -1* source_doc.qty target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_receipt = source_doc.purchase_receipt target_doc.po_detail = source_doc.po_detail diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a832ab771f..bd1f258a8d 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -169,7 +169,7 @@ class StockController(AccountsController): else: is_expense_account = frappe.db.get_value("Account", item.get("expense_account"), "report_type")=="Profit and Loss" - if self.doctype not in ("Purchase Receipt", "Stock Reconciliation", "Stock Entry") and not is_expense_account: + if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account: frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account") .format(item.get("expense_account"))) if is_expense_account and not item.get("cost_center"): diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index a2ee1b75b9..b93857d3b2 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -516,10 +516,10 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ ); } - if(this.frm.doc.doctype == "Sales Invoice") { + if(this.frm.doc.doctype == "Sales Invoice" || this.frm.doc.doctype == "Purchase Invoice") { frappe.model.round_floats_in(this.frm.doc, ["paid_amount"]); - if(this.frm.doc.is_pos) { + if(this.frm.doc.is_pos || this.frm.doc.is_cash) { if(!this.frm.doc.paid_amount || update_paid_amount===undefined || update_paid_amount) { this.frm.doc.paid_amount = flt(total_amount_to_pay); } From 821f57164060b2f71635834ba045c35eaa25955b Mon Sep 17 00:00:00 2001 From: jof2jc Date: Thu, 11 Feb 2016 00:00:30 +0700 Subject: [PATCH 02/15] added payments, make receipt field --- .../purchase_invoice/purchase_invoice.json | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index df47ba7656..dc21b3f848 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -2957,6 +2957,26 @@ "submit": 0, "write": 0 }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 1, + "print": 0, + "read": 1, + "report": 0, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 1 + }, { "amend": 0, "apply_user_permissions": 0, From c204775fe278c988116259e0896a50c64027e769 Mon Sep 17 00:00:00 2001 From: jof2jc Date: Thu, 11 Feb 2016 01:14:06 +0700 Subject: [PATCH 03/15] Added posting_time in purchase invoice --- .../doctype/purchase_invoice/purchase_invoice.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index dc21b3f848..b2657e1d7f 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -3004,6 +3004,11 @@ "sort_field": "modified", "sort_order": "DESC", "timeline_field": "supplier", +<<<<<<< f2ba6af60b37b968f1aff340a88765a917c3f133 "title_field": "title", "track_seen": 0 -} \ No newline at end of file +} +======= + "title_field": "title" +} +>>>>>>> Added posting_time in purchase invoice From bd5fc8ecbee4957fc8d7204fff35d66cd6d95b94 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 16 Mar 2016 11:42:42 +0530 Subject: [PATCH 04/15] [fixes] add posting time --- .../purchase_invoice/purchase_invoice.json | 356 ++++++++---------- 1 file changed, 152 insertions(+), 204 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index b2657e1d7f..d35d0c4b7b 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -262,9 +262,6 @@ "label": "Company", "length": 0, "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", "permlevel": 0, "print_hide": 1, "print_hide_if_no_value": 0, @@ -812,6 +809,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Make Receipt (Update Stock)", @@ -1698,6 +1696,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Payments", @@ -1722,6 +1721,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Mode of Payment", @@ -1748,6 +1748,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Cash/Bank Account", @@ -1773,6 +1774,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -1796,6 +1798,7 @@ "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Paid Amount", @@ -1821,6 +1824,7 @@ "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Paid Amount (Company Currency)", @@ -2172,31 +2176,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "language", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Print Language", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 1, "bold": 0, @@ -2438,21 +2417,48 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "mode_of_payment", - "fieldtype": "Link", + "fieldname": "posting_time", + "fieldtype": "Time", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Mode of Payment", + "label": "Posting Time", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "print_width": "100px", + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "100px" + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "fiscal_year", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Fiscal Year", "length": 0, "no_copy": 0, - "oldfieldname": "mode_of_payment", + "oldfieldname": "fiscal_year", "oldfieldtype": "Select", - "options": "Mode of Payment", + "options": "Fiscal Year", "permlevel": 0, - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, @@ -2544,35 +2550,8 @@ "allow_on_submit": 1, "bold": 0, "collapsible": 0, - "depends_on": "is_recurring", - "description": "", - "fieldname": "recurring_id", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Document", - "length": 0, - "no_copy": 1, - "options": "Purchase Invoice", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name", - "description": "", + "depends_on": "eval:doc.is_recurring==1", + "description": "Select the period when the invoice will be generated automatically", "fieldname": "recurring_type", "fieldtype": "Select", "hidden": 0, @@ -2580,7 +2559,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Frequency", + "label": "Recurring Type", "length": 0, "no_copy": 1, "options": "Monthly\nQuarterly\nHalf-yearly\nYearly", @@ -2598,16 +2577,16 @@ "allow_on_submit": 1, "bold": 0, "collapsible": 0, - "depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name", - "description": "", - "fieldname": "repeat_on_day_of_month", - "fieldtype": "Int", + "depends_on": "eval:doc.is_recurring==1", + "description": "Start date of current invoice's period", + "fieldname": "from_date", + "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Repeat on Day of Month", + "label": "From Date", "length": 0, "no_copy": 1, "permlevel": 0, @@ -2624,16 +2603,16 @@ "allow_on_submit": 1, "bold": 0, "collapsible": 0, - "depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name", - "description": "", - "fieldname": "end_date", + "depends_on": "eval:doc.is_recurring==1", + "description": "End date of current invoice's period", + "fieldname": "to_date", "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Recurring Ends On", + "label": "To Date", "length": 0, "no_copy": 1, "permlevel": 0, @@ -2676,20 +2655,19 @@ "allow_on_submit": 1, "bold": 0, "collapsible": 0, - "depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name", - "description": "", - "fieldname": "notify_by_email", - "fieldtype": "Check", + "depends_on": "eval:doc.is_recurring==1", + "description": "The day of the month on which auto invoice will be generated e.g. 05, 28 etc", + "fieldname": "repeat_on_day_of_month", + "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Notify by email", + "label": "Repeat on Day of Month", "length": 0, "no_copy": 1, "permlevel": 0, - "precision": "", "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, @@ -2703,19 +2681,18 @@ "allow_on_submit": 1, "bold": 0, "collapsible": 0, - "depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name", - "description": "", - "fieldname": "notification_email_address", - "fieldtype": "Code", + "depends_on": "eval:doc.is_recurring==1", + "description": "The date on which recurring invoice will be stop", + "fieldname": "end_date", + "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Notification Email Address", + "label": "End Date", "length": 0, "no_copy": 1, - "options": "Email", "permlevel": 0, "print_hide": 1, "print_hide_if_no_value": 0, @@ -2726,33 +2703,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name", - "fieldname": "recurring_print_format", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Recurring Print Format", - "length": 0, - "no_copy": 0, - "options": "Print Format", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -2778,25 +2728,51 @@ "width": "50%" }, { - "allow_on_submit": 1, + "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "depends_on": "is_recurring", - "description": "", - "fieldname": "from_date", + "depends_on": "eval:doc.is_recurring==1", + "description": "The date on which next invoice will be generated. It is generated on submit.", + "fieldname": "next_date", "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "From Date", + "label": "Next Date", "length": 0, "no_copy": 1, "permlevel": 0, "print_hide": 1, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:doc.is_recurring==1", + "description": "The unique id for tracking all recurring invoices. It is generated on submit.", + "fieldname": "recurring_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Recurring Id", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -2807,16 +2783,16 @@ "allow_on_submit": 1, "bold": 0, "collapsible": 0, - "depends_on": "is_recurring", - "description": "", - "fieldname": "to_date", - "fieldtype": "Date", + "depends_on": "eval:doc.is_recurring==1", + "description": "Enter email id separated by commas, invoice will be mailed automatically on particular date", + "fieldname": "notification_email_address", + "fieldtype": "Small Text", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "To Date", + "label": "Notification Email Address", "length": 0, "no_copy": 1, "permlevel": 0, @@ -2833,20 +2809,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "depends_on": "is_recurring", - "description": "", - "fieldname": "next_date", - "fieldtype": "Date", + "depends_on": "eval:doc.is_recurring==1", + "fieldname": "recurring_print_format", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Next Date", + "label": "Recurring Print Format", "length": 0, - "no_copy": 1, + "no_copy": 0, + "options": "Print Format", "permlevel": 0, - "print_hide": 1, + "precision": "", + "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, @@ -2875,19 +2852,18 @@ "permissions": [ { "amend": 1, - "apply_user_permissions": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 0, - "email": 0, - "export": 1, + "email": 1, + "export": 0, "if_owner": 0, "import": 0, "permlevel": 0, "print": 1, "read": 1, - "report": 0, - "restrict": 0, + "report": 1, "role": "Accounts User", "set_user_permissions": 0, "share": 1, @@ -2896,49 +2872,7 @@ }, { "amend": 0, - "apply_user_permissions": 1, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "restrict": 0, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, - { - "amend": 1, "apply_user_permissions": 0, - "cancel": 1, - "create": 1, - "delete": 0, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "restrict": 0, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 1, "cancel": 0, "create": 0, "delete": 0, @@ -2950,7 +2884,46 @@ "print": 1, "read": 1, "report": 1, - "restrict": 0, + "role": "Purchase User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 0 + }, + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, "role": "Auditor", "set_user_permissions": 0, "share": 0, @@ -2976,39 +2949,14 @@ "share": 0, "submit": 0, "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 0, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 } ], "read_only": 0, "read_only_onload": 1, - "search_fields": "posting_date, supplier, bill_no, base_grand_total, outstanding_amount", + "search_fields": "posting_date, supplier, fiscal_year, bill_no, base_grand_total, outstanding_amount", "sort_field": "modified", "sort_order": "DESC", "timeline_field": "supplier", -<<<<<<< f2ba6af60b37b968f1aff340a88765a917c3f133 "title_field": "title", "track_seen": 0 } -======= - "title_field": "title" -} ->>>>>>> Added posting_time in purchase invoice From 4bd34a58ba93f29bf531ea150d6c81d3fc72ed1a Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 18 Mar 2016 14:40:03 +0530 Subject: [PATCH 05/15] [fixes] fix perpetual inventory --- .../purchase_invoice/purchase_invoice.js | 139 ++++++------------ .../purchase_invoice/purchase_invoice.json | 42 +----- .../purchase_invoice/purchase_invoice.py | 48 ++++-- .../purchase_invoice_item.json | 11 +- .../purchase_common/purchase_common.js | 43 +++++- .../public/js/controllers/taxes_and_totals.js | 2 +- .../purchase_receipt/purchase_receipt.js | 42 ------ 7 files changed, 139 insertions(+), 188 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 7ac5400a64..7b1b8b19a2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -25,99 +25,50 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ // Show / Hide button this.show_general_ledger(); - if(!doc.is_return) { - if(doc.docstatus==1) { - if(doc.outstanding_amount > 0) { - this.frm.add_custom_button(__('Payment'), this.make_bank_entry, __("Make")); - cur_frm.page.set_inner_btn_group_as_primary(__("Make")); - } + if(!doc.is_return && doc.docstatus==1) { + if(doc.outstanding_amount > 0) { + this.frm.add_custom_button(__('Payment'), this.make_bank_entry, __("Make")); + cur_frm.page.set_inner_btn_group_as_primary(__("Make")); + } - if (!doc.make_receipt) { - if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { - cur_frm.add_custom_button(__('Debit Note'), this.make_debit_note, __("Make")); - } - } - else { - cur_frm.add_custom_button(__('Return'), this.make_debit_note, __("Make")); - } - + if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { + cur_frm.add_custom_button(doc.update_stock ? __('Purchase Return') : __('Debit Note'), + this.make_debit_note, __("Make")); } - - if(doc.docstatus===0) { - cur_frm.add_custom_button(__('Purchase Order'), function() { - frappe.model.map_current_doc({ - method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice", - source_doctype: "Purchase Order", - get_query_filters: { - supplier: cur_frm.doc.supplier || undefined, - docstatus: 1, - status: ["!=", "Closed"], - per_billed: ["<", 99.99], - company: cur_frm.doc.company - } - }) - }, __("Get items from")); - - cur_frm.add_custom_button(__('Purchase Receipt'), function() { - frappe.model.map_current_doc({ - method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice", - source_doctype: "Purchase Receipt", - get_query_filters: { - supplier: cur_frm.doc.supplier || undefined, - docstatus: 1, - status: ["!=", "Closed"], - company: cur_frm.doc.company - } - }) - }, __("Get items from")); - } - } - if(doc.docstatus==1 && doc.make_receipt==1) { + + if(doc.update_stock==1) { this.show_stock_ledger(); } - - }, - - received_qty: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - - item.qty = (item.qty < item.received_qty) ? item.qty : item.received_qty; - this.qty(doc, cdt, cdn); - }, - - qty: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - - if(!(item.received_qty || item.rejected_qty) && item.qty) { - item.received_qty = item.qty; } + + if(doc.docstatus===0) { + cur_frm.add_custom_button(__('Purchase Order'), function() { + frappe.model.map_current_doc({ + method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice", + source_doctype: "Purchase Order", + get_query_filters: { + supplier: cur_frm.doc.supplier || undefined, + docstatus: 1, + status: ["!=", "Closed"], + per_billed: ["<", 99.99], + company: cur_frm.doc.company + } + }) + }, __("Get items from")); - if(item.qty > item.received_qty) { - msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "qty", item.name)), - __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])) - item.qty = item.rejected_qty = 0.0; - } else { - item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); + cur_frm.add_custom_button(__('Purchase Receipt'), function() { + frappe.model.map_current_doc({ + method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice", + source_doctype: "Purchase Receipt", + get_query_filters: { + supplier: cur_frm.doc.supplier || undefined, + docstatus: 1, + status: ["!=", "Closed"], + company: cur_frm.doc.company + } + }) + }, __("Get items from")); } - - this._super(doc, cdt, cdn); - }, - - rejected_qty: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]); - - if(item.rejected_qty > item.received_qty) { - msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "rejected_qty", item.name)), - __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])); - item.qty = item.rejected_qty = 0.0; - } else { - item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); - } - - this.qty(doc, cdt, cdn); }, supplier: function() { @@ -156,11 +107,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ } }, - is_cash: function() { + is_paid: function() { cur_frm.cscript.hide_fields(this.frm.doc); - if(cint(this.frm.doc.is_cash)) { + if(cint(this.frm.doc.is_paid)) { if(!this.frm.doc.company) { - this.frm.set_value("is_cash", 0); + this.frm.set_value("x", 0); msgprint(__("Please specify Company to proceed")); } } @@ -216,7 +167,7 @@ cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); cur_frm.cscript.hide_fields = function(doc) { par_flds = ['due_date', 'is_opening', 'advances_section', 'from_date', 'to_date']; - if(cint(doc.is_cash) == 1) { + if(cint(doc.is_paid) == 1) { hide_field(par_flds); } else { for (i in par_flds) { @@ -226,21 +177,21 @@ cur_frm.cscript.hide_fields = function(doc) { } - item_flds_stock = ['sc_wh', 'received_qty', 'rejected_qty']; + item_flds_stock = ['warehouse_section', 'received_qty', 'rejected_qty']; //item_flds_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse'] cur_frm.fields_dict['items'].grid.set_column_disp(item_flds_stock, - (cint(doc.make_receipt)==1 ? true : false)); + (cint(doc.update_stock)==1 ? true : false)); cur_frm.refresh_fields(); } -cur_frm.cscript.make_receipt = function(doc, dt, dn) { +cur_frm.cscript.update_stock = function(doc, dt, dn) { cur_frm.cscript.hide_fields(doc, dt, dn); } cur_frm.cscript.mode_of_payment = function(doc) { - if(doc.is_cash) { + if(doc.is_paid) { return cur_frm.call({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", args: { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d35d0c4b7b..7f6a7f155c 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -276,14 +276,15 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "is_return", + "default": "0", + "fieldname": "is_paid", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Is Return", + "label": "Is Paid", "length": 0, "no_copy": 0, "permlevel": 0, @@ -805,14 +806,14 @@ "bold": 0, "collapsible": 0, "default": "0", - "fieldname": "make_receipt", + "fieldname": "update_stock", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Make Receipt (Update Stock)", + "label": "Update Stock", "length": 0, "no_copy": 0, "permlevel": 0, @@ -1691,8 +1692,8 @@ "bold": 0, "collapsible": 1, "collapsible_depends_on": "paid_amount", - "depends_on": "eval:doc.is_cash===1", - "fieldname": "sc_br_payments", + "depends_on": "eval:doc.is_paid===1", + "fieldname": "payments_section", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -2440,33 +2441,6 @@ "unique": 0, "width": "100px" }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Fiscal Year", - "length": 0, - "no_copy": 0, - "oldfieldname": "fiscal_year", - "oldfieldtype": "Select", - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -2953,7 +2927,7 @@ ], "read_only": 0, "read_only_onload": 1, - "search_fields": "posting_date, supplier, fiscal_year, bill_no, base_grand_total, outstanding_amount", + "search_fields": "posting_date, supplier, bill_no, base_grand_total, outstanding_amount", "sort_field": "modified", "sort_order": "DESC", "timeline_field": "supplier", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index ba80bcfbb6..fcf2667235 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -12,6 +12,7 @@ from erpnext.controllers.buying_controller import BuyingController from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po +from erpnext.controllers.stock_controller import get_warehouse_account form_grid_templates = { @@ -47,11 +48,11 @@ class PurchaseInvoice(BuyingController): self.validate_advance_jv("Purchase Order") # validate cash purchase - if (self.is_cash == 1): + if (self.is_paid == 1): self.validate_cash() # validate stock items - if (self.make_receipt == 1): + if (self.update_stock == 1): self.validate_purchase_return() self.validate_rejected_warehouse() self.validate_accepted_rejected_qty() @@ -286,7 +287,7 @@ class PurchaseInvoice(BuyingController): reconcile_against_document(lst) def update_status_updater_args(self): - if cint(self.make_receipt): + if cint(self.update_stock): self.status_updater.extend([{ 'source_dt': 'Purchase Invoice Item', 'target_dt': 'Purchase Order Item', @@ -300,7 +301,7 @@ class PurchaseInvoice(BuyingController): # 'percent_join_field': 'prevdoc_docname', 'overflow_type': 'receipt', 'extra_cond': """ and exists(select name from `tabPurchase Invoice` - where name=`tabPurchase Invoice Item`.parent and make_receipt = 1)""" + where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""" }, { 'source_dt': 'Purchase Invoice Item', @@ -362,17 +363,17 @@ class PurchaseInvoice(BuyingController): self.company, self.base_grand_total) # make purchase receipt - if (self.make_receipt == 1): + if (self.update_stock == 1): # from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_stock_ledger self.update_stock_ledger() - self.make_gl_entries() from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit update_serial_nos_after_submit(self, "items") self.update_status_updater_args() self.update_prevdoc_status() # this sequence because outstanding may get -negative - self.make_gl_entries1() + self.make_gl_entries() + if not self.is_return: self.update_against_document_in_jv() self.update_prevdoc_status() @@ -380,6 +381,7 @@ class PurchaseInvoice(BuyingController): self.update_billing_status_in_pr() self.update_project() +<<<<<<< 1b49ed56bdbd4a6cdf93ede3bfa252e5a954ed41 def validate_asset(self): for d in self.get("items"): @@ -408,6 +410,22 @@ class PurchaseInvoice(BuyingController): if self.docstatus==1 and not asset.supplier: frappe.db.set_value("Asset", asset.name, "supplier", self.supplier) +======= + + def on_cancel(self): + if not self.is_return: + from erpnext.accounts.utils import remove_against_link_from_jv + remove_against_link_from_jv(self.doctype, self.name) + + self.update_prevdoc_status() + self.update_billing_status_for_zero_amount_refdoc("Purchase Order") + self.update_billing_status_in_pr() + + self.update_stock_ledger() + self.make_gl_entries_on_cancel() + self.update_project() + +>>>>>>> [fixes] fix perpetual inventory def make_gl_entries(self): auto_accounting_for_stock = \ cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) @@ -418,7 +436,7 @@ class PurchaseInvoice(BuyingController): gl_entries = [] # Make Cash GL Entries - if cint(self.is_cash) and self.cash_bank_account and self.paid_amount: + if cint(self.is_paid) and self.cash_bank_account and self.paid_amount: bank_account_currency = get_account_currency(self.cash_bank_account) # CASH, make payment entries gl_entries.append( @@ -434,6 +452,7 @@ class PurchaseInvoice(BuyingController): "against_voucher_type": self.doctype, }, self.party_account_currency) ) + gl_entries.append( self.get_gl_dict({ "account": self.cash_bank_account, @@ -443,7 +462,7 @@ class PurchaseInvoice(BuyingController): if bank_account_currency==self.company_currency else self.paid_amount }, bank_account_currency) ) - + # parent's gl entry if self.grand_total: # Didnot use base_grand_total to book rounding loss gle @@ -463,7 +482,6 @@ class PurchaseInvoice(BuyingController): "against_voucher_type": self.doctype, }, self.party_account_currency) ) - # tax table gl entries valuation_tax = {} for tax in self.get("taxes"): @@ -483,7 +501,6 @@ class PurchaseInvoice(BuyingController): "cost_center": tax.cost_center }, account_currency) ) - # accumulate valuation tax if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if auto_accounting_for_stock and not tax.cost_center: @@ -491,16 +508,17 @@ class PurchaseInvoice(BuyingController): valuation_tax.setdefault(tax.cost_center, 0) valuation_tax[tax.cost_center] += \ (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) - # item gl entries negative_expense_to_be_booked = 0.0 stock_items = self.get_stock_items() + warehouse_account = get_warehouse_account() + for item in self.get("items"): if flt(item.base_net_amount): account_currency = get_account_currency(item.expense_account) gl_entries.append( self.get_gl_dict({ - "account": item.expense_account, + "account": item.expense_account if not self.update_stock else warehouse_account[item.warehouse]["name"], "against": self.supplier, "debit": item.base_net_amount, "debit_in_account_currency": item.base_net_amount \ @@ -508,7 +526,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center }, account_currency) ) - + if auto_accounting_for_stock and self.is_opening == "No" and \ item.item_code in stock_items and item.item_tax_amount: # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt @@ -583,7 +601,7 @@ class PurchaseInvoice(BuyingController): "cost_center": self.write_off_cost_center }) ) - + if gl_entries: from erpnext.accounts.general_ledger import make_gl_entries make_gl_entries(gl_entries, cancel=(self.docstatus == 2)) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index cc9e1d3f56..28b84ebbcd 100755 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -221,6 +221,7 @@ "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Received Qty", @@ -271,6 +272,7 @@ "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Rejected Qty", @@ -815,10 +817,11 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "sc_wh", + "fieldname": "warehouse_section", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Warehouse", @@ -844,6 +847,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Accepted Warehouse", @@ -869,6 +873,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Rejected Warehouse", @@ -894,6 +899,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Batch No", @@ -919,6 +925,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "", @@ -943,6 +950,7 @@ "fieldtype": "Text", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Serial No", @@ -967,6 +975,7 @@ "fieldtype": "Text", "hidden": 1, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Rejected Serial No", diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.js b/erpnext/buying/doctype/purchase_common/purchase_common.js index 2f1959b803..5dcb0122f8 100644 --- a/erpnext/buying/doctype/purchase_common/purchase_common.js +++ b/erpnext/buying/doctype/purchase_common/purchase_common.js @@ -42,7 +42,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ this.frm.set_query("item_code", "items", function() { if(me.frm.doc.is_subcontracted == "Yes") { - return{ + return{ query: "erpnext.controllers.queries.item_query", filters:{ 'is_sub_contracted_item': 1 } } @@ -114,8 +114,49 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ }, qty: function(doc, cdt, cdn) { + if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) { + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["qty", "received_qty"]); + + if(!(item.received_qty || item.rejected_qty) && item.qty) { + item.received_qty = item.qty; + } + + if(item.qty > item.received_qty) { + msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "qty", item.name)), + __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])) + item.qty = item.rejected_qty = 0.0; + } else { + item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); + } + } + this._super(doc, cdt, cdn); this.conversion_factor(doc, cdt, cdn); + + }, + + received_qty: function(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["qty", "received_qty"]); + + item.qty = (item.qty < item.received_qty) ? item.qty : item.received_qty; + this.qty(doc, cdt, cdn); + }, + + rejected_qty: function(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]); + + if(item.rejected_qty > item.received_qty) { + msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "rejected_qty", item.name)), + __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])); + item.qty = item.rejected_qty = 0.0; + } else { + item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); + } + + this.qty(doc, cdt, cdn); }, conversion_factor: function(doc, cdt, cdn) { diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b93857d3b2..b4e712a5f5 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -519,7 +519,7 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ if(this.frm.doc.doctype == "Sales Invoice" || this.frm.doc.doctype == "Purchase Invoice") { frappe.model.round_floats_in(this.frm.doc, ["paid_amount"]); - if(this.frm.doc.is_pos || this.frm.doc.is_cash) { + if(this.frm.doc.is_pos || this.frm.doc.is_paid) { if(!this.frm.doc.paid_amount || update_paid_amount===undefined || update_paid_amount) { this.frm.doc.paid_amount = flt(total_amount_to_pay); } diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 2a7629e0e0..c2bbfbf64f 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -76,48 +76,6 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes"); }, - received_qty: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - - item.qty = (item.qty < item.received_qty) ? item.qty : item.received_qty; - this.qty(doc, cdt, cdn); - }, - - qty: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - - if(!(item.received_qty || item.rejected_qty) && item.qty) { - item.received_qty = item.qty; - } - - if(item.qty > item.received_qty) { - msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "qty", item.name)), - __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])) - item.qty = item.rejected_qty = 0.0; - } else { - item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); - } - - this._super(doc, cdt, cdn); - }, - - rejected_qty: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]); - - if(item.rejected_qty > item.received_qty) { - msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "rejected_qty", item.name)), - __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])); - item.qty = item.rejected_qty = 0.0; - } else { - item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); - } - - this.qty(doc, cdt, cdn); - }, - make_purchase_invoice: function() { frappe.model.open_mapped_doc({ method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice", From 2a0e2fe95c4b872cabaa9b2aa21322f5e78628a6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 21 Mar 2016 16:28:45 +0530 Subject: [PATCH 06/15] [test-case] test update stock and payment --- .../purchase_invoice/test_purchase_invoice.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b0ac627f5f..2ac4e12c84 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -314,6 +314,33 @@ class TestPurchaseInvoice(unittest.TestCase): where voucher_type='Sales Invoice' and voucher_no=%s""", pi.name) self.assertFalse(gle) + + def purchase_invoice_gl_entry_with_perpetual_inventory(self): + """ + 1. create purchase invoice with update stock as true + 2. get gl entry against purchase invoice + 3. check accounts + """ + set_perpetual_inventory() + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), + posting_time=frappe.utils.nowtime()) + + gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", pi.name, as_dict=1) + + self.assertTrue(gl_entries) + + expected_gl_entries = dict((d[0], d) for d in [ + [pi.credit_to, 0.0, 250.0], + [pi.get("items")[0]["warehouse"], 250.0, 0.0] + ]) + + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_gl_entries[gle.account][0], gle.account) + self.assertEquals(expected_gl_entries[gle.account][1], gle.debit) + self.assertEquals(expected_gl_entries[gle.account][2], gle.credit) def make_purchase_invoice(**args): pi = frappe.new_doc("Purchase Invoice") @@ -322,6 +349,8 @@ def make_purchase_invoice(**args): pi.posting_date = args.posting_date if args.posting_time: pi.posting_time = args.posting_time + if args.update_stock: + pi.update_stock = 1 pi.company = args.company or "_Test Company" pi.supplier = args.supplier or "_Test Supplier" pi.currency = args.currency or "INR" From 43520f93a1452e389ef2d2dbd0e27e8a9ff5b3f6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 21 Mar 2016 18:32:48 +0530 Subject: [PATCH 07/15] [fixes] test cases, code cleanup. --- .../purchase_invoice/purchase_invoice.js | 34 +++-------- .../purchase_invoice/purchase_invoice.json | 58 ++++++++++++++++++- .../purchase_invoice/purchase_invoice.py | 19 +----- .../purchase_invoice/test_purchase_invoice.py | 37 +++++++++--- .../doctype/sales_invoice/sales_invoice.js | 33 +++-------- .../doctype/sales_invoice/sales_invoice.py | 15 ----- erpnext/controllers/accounts_controller.py | 20 +++++++ 7 files changed, 120 insertions(+), 96 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 7b1b8b19a2..ebda170404 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -111,7 +111,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ cur_frm.cscript.hide_fields(this.frm.doc); if(cint(this.frm.doc.is_paid)) { if(!this.frm.doc.company) { - this.frm.set_value("x", 0); msgprint(__("Please specify Company to proceed")); } } @@ -165,22 +164,21 @@ cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); // Hide Fields // ------------ cur_frm.cscript.hide_fields = function(doc) { - par_flds = ['due_date', 'is_opening', 'advances_section', 'from_date', 'to_date']; + parent_fields = ['due_date', 'is_opening', 'advances_section', 'from_date', 'to_date']; if(cint(doc.is_paid) == 1) { - hide_field(par_flds); + hide_field(parent_fields); } else { - for (i in par_flds) { - var docfield = frappe.meta.docfield_map[doc.doctype][par_flds[i]]; - if(!docfield.hidden) unhide_field(par_flds[i]); + for (i in parent_fields) { + var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]]; + if(!docfield.hidden) unhide_field(parent_fields[i]); } } - item_flds_stock = ['warehouse_section', 'received_qty', 'rejected_qty']; + item_fields_stock = ['warehouse_section', 'received_qty', 'rejected_qty']; - //item_flds_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse'] - cur_frm.fields_dict['items'].grid.set_column_disp(item_flds_stock, + cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock, (cint(doc.update_stock)==1 ? true : false)); cur_frm.refresh_fields(); @@ -190,24 +188,6 @@ cur_frm.cscript.update_stock = function(doc, dt, dn) { cur_frm.cscript.hide_fields(doc, dt, dn); } -cur_frm.cscript.mode_of_payment = function(doc) { - if(doc.is_paid) { - return cur_frm.call({ - method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", - args: { - "mode_of_payment": doc.mode_of_payment, - "company": doc.company - }, - callback: function(r, rt) { - if(r.message) { - cur_frm.set_value("cash_bank_account", r.message["account"]); - } - - } - }); - } -} - cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { return { filters: [ diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 7f6a7f155c..f76442b0be 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -116,6 +116,31 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "is_paid", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Is Paid", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -277,14 +302,14 @@ "bold": 0, "collapsible": 0, "default": "0", - "fieldname": "is_paid", + "fieldname": "is_return", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Is Paid", + "label": "Is Return", "length": 0, "no_copy": 0, "permlevel": 0, @@ -2624,7 +2649,34 @@ "search_index": 0, "set_only_once": 0, "unique": 0 - }, + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name", + "description": "", + "fieldname": "notify_by_email", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Notify by email", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 1, "bold": 0, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fcf2667235..728088bd06 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -354,7 +354,7 @@ class PurchaseInvoice(BuyingController): # self.bk_flush_supp_wh(sl_entries) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - + def on_submit(self): self.check_prev_docstatus() self.validate_asset() @@ -381,7 +381,6 @@ class PurchaseInvoice(BuyingController): self.update_billing_status_in_pr() self.update_project() -<<<<<<< 1b49ed56bdbd4a6cdf93ede3bfa252e5a954ed41 def validate_asset(self): for d in self.get("items"): @@ -409,23 +408,7 @@ class PurchaseInvoice(BuyingController): if self.docstatus==1 and not asset.supplier: frappe.db.set_value("Asset", asset.name, "supplier", self.supplier) - -======= - def on_cancel(self): - if not self.is_return: - from erpnext.accounts.utils import remove_against_link_from_jv - remove_against_link_from_jv(self.doctype, self.name) - - self.update_prevdoc_status() - self.update_billing_status_for_zero_amount_refdoc("Purchase Order") - self.update_billing_status_in_pr() - - self.update_stock_ledger() - self.make_gl_entries_on_cancel() - self.update_project() - ->>>>>>> [fixes] fix perpetual inventory def make_gl_entries(self): auto_accounting_for_stock = \ cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2ac4e12c84..54053735d3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -315,12 +315,7 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertFalse(gle) - def purchase_invoice_gl_entry_with_perpetual_inventory(self): - """ - 1. create purchase invoice with update stock as true - 2. get gl entry against purchase invoice - 3. check accounts - """ + def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self): set_perpetual_inventory() pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), posting_time=frappe.utils.nowtime()) @@ -334,7 +329,30 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gl_entries = dict((d[0], d) for d in [ [pi.credit_to, 0.0, 250.0], - [pi.get("items")[0]["warehouse"], 250.0, 0.0] + [pi.items[0].warehouse, 250.0, 0.0] + ]) + + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_gl_entries[gle.account][0], gle.account) + self.assertEquals(expected_gl_entries[gle.account][1], gle.debit) + self.assertEquals(expected_gl_entries[gle.account][2], gle.credit) + + def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self): + set_perpetual_inventory() + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), + posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1) + + gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", pi.name, as_dict=1) + + self.assertTrue(gl_entries) + + expected_gl_entries = dict((d[0], d) for d in [ + [pi.credit_to, 250, 250.0], + [pi.items[0].warehouse, 250.0, 0.0], + ["Cash - _TC", 0.0, 250.0] ]) for i, gle in enumerate(gl_entries): @@ -351,6 +369,11 @@ def make_purchase_invoice(**args): pi.posting_time = args.posting_time if args.update_stock: pi.update_stock = 1 + if args.is_paid: + pi.is_paid = 1 + if args.cash_bank_account: + pi.cash_bank_account=args.cash_bank_account + pi.company = args.company or "_Test Company" pi.supplier = args.supplier or "_Test Supplier" pi.currency = args.currency or "INR" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index f6f746a97f..18049cca37 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -278,20 +278,20 @@ $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_ // Hide Fields // ------------ cur_frm.cscript.hide_fields = function(doc) { - par_flds = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances_received', + parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances_received', 'advances', 'sales_partner', 'commission_rate', 'total_commission', 'advances', 'from_date', 'to_date']; if(cint(doc.is_pos) == 1) { - hide_field(par_flds); + hide_field(parent_fields); } else { - for (i in par_flds) { - var docfield = frappe.meta.docfield_map[doc.doctype][par_flds[i]]; - if(!docfield.hidden) unhide_field(par_flds[i]); + for (i in parent_fields) { + var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]]; + if(!docfield.hidden) unhide_field(parent_fields[i]); } } - item_flds_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse'] - cur_frm.fields_dict['items'].grid.set_column_disp(item_flds_stock, + item_fields_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse'] + cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock, (cint(doc.update_stock)==1 ? true : false)); // India related fields @@ -303,25 +303,6 @@ cur_frm.cscript.hide_fields = function(doc) { cur_frm.refresh_fields(); } - -cur_frm.cscript.mode_of_payment = function(doc) { - if(doc.is_pos) { - return cur_frm.call({ - method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", - args: { - "mode_of_payment": doc.mode_of_payment, - "company": doc.company - }, - callback: function(r, rt) { - if(r.message) { - cur_frm.set_value("cash_bank_account", r.message["account"]); - } - - } - }); - } -} - cur_frm.cscript.update_stock = function(doc, dt, dn) { cur_frm.cscript.hide_fields(doc, dt, dn); } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bc1d73d15c..265c38b91a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -461,21 +461,6 @@ class SalesInvoice(SellingController): d.income_account = disposal_account - def on_update(self): - if cint(self.is_pos) == 1: - if flt(self.paid_amount) == 0: - if self.cash_bank_account: - frappe.db.set(self, 'paid_amount', - flt(flt(self.grand_total) - flt(self.write_off_amount), self.precision("paid_amount"))) - else: - # show message that the amount is not paid - frappe.db.set(self,'paid_amount',0) - frappe.msgprint(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) - else: - frappe.db.set(self,'paid_amount',0) - - frappe.db.set(self, 'base_paid_amount', - flt(self.paid_amount*self.conversion_rate, self.precision("base_paid_amount"))) def check_prev_docstatus(self): for d in self.get('items'): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 50d4d8a2ab..cc64a26aa8 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -54,6 +54,26 @@ class AccountsController(TransactionBase): if not self.get("__islocal"): validate_recurring_document(self) convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) + + self.validate_paid_amount() + + def validate_paid_amount(self): + if hasattr(self, "is_pos") or hasattr(self, "is_paid"): + is_paid = self.get("is_pos") or self.get("is_paid") + if cint(is_paid) == 1: + if flt(self.paid_amount) == 0: + if self.cash_bank_account: + frappe.db.set(self, 'paid_amount', + flt(flt(self.grand_total) - flt(self.write_off_amount), self.precision("paid_amount"))) + else: + # show message that the amount is not paid + frappe.db.set(self,'paid_amount',0) + frappe.msgprint(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) + else: + frappe.db.set(self,'paid_amount',0) + + frappe.db.set(self, 'base_paid_amount', + flt(self.paid_amount*self.conversion_rate, self.precision("base_paid_amount"))) def on_update_after_submit(self): if self.meta.get_field("is_recurring"): From d697f3d6a3bde2fc216c385147097a9347940b79 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 23 Mar 2016 11:25:54 +0530 Subject: [PATCH 08/15] [minor][fix] reset is_paid if company not selected --- .../accounts/doctype/purchase_invoice/purchase_invoice.js | 1 + .../accounts/doctype/purchase_invoice/purchase_invoice.json | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index ebda170404..123f4ea661 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -111,6 +111,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ cur_frm.cscript.hide_fields(this.frm.doc); if(cint(this.frm.doc.is_paid)) { if(!this.frm.doc.company) { + cur_frm.set_value("is_paid", 0) msgprint(__("Please specify Company to proceed")); } } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index f76442b0be..46843b3f34 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -287,6 +287,7 @@ "label": "Company", "length": 0, "no_copy": 0, + "options": "Company", "permlevel": 0, "print_hide": 1, "print_hide_if_no_value": 0, @@ -2649,7 +2650,7 @@ "search_index": 0, "set_only_once": 0, "unique": 0 - }, + }, { "allow_on_submit": 1, "bold": 0, @@ -2676,7 +2677,7 @@ "search_index": 0, "set_only_once": 0, "unique": 0 - }, + }, { "allow_on_submit": 1, "bold": 0, From e29248bcb341ea38c897592d005698e864a323ed Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 4 Apr 2016 11:55:52 +0530 Subject: [PATCH 09/15] [fixes] test-case to validate quantity after update stock and purchase return and code-cleaning --- .../purchase_invoice/purchase_invoice.js | 8 +-- .../purchase_invoice/purchase_invoice.py | 42 ++------------- .../purchase_invoice/test_purchase_invoice.py | 18 +++++++ erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/buying_controller.py | 51 ++++++++++++++++++- .../purchase_receipt/purchase_receipt.py | 47 ----------------- 6 files changed, 77 insertions(+), 91 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 123f4ea661..03d0180105 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -16,7 +16,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ } } - cur_frm.cscript.hide_fields(this.frm.doc); + hide_fields(this.frm.doc); }, refresh: function(doc) { @@ -108,7 +108,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }, is_paid: function() { - cur_frm.cscript.hide_fields(this.frm.doc); + hide_fields(this.frm.doc); if(cint(this.frm.doc.is_paid)) { if(!this.frm.doc.company) { cur_frm.set_value("is_paid", 0) @@ -164,7 +164,7 @@ cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); // Hide Fields // ------------ -cur_frm.cscript.hide_fields = function(doc) { +function hide_fields(doc) { parent_fields = ['due_date', 'is_opening', 'advances_section', 'from_date', 'to_date']; if(cint(doc.is_paid) == 1) { @@ -186,7 +186,7 @@ cur_frm.cscript.hide_fields = function(doc) { } cur_frm.cscript.update_stock = function(doc, dt, dn) { - cur_frm.cscript.hide_fields(doc, dt, dn); + hide_fields(doc, dt, dn); } cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 728088bd06..14f157ecd6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cstr, cint, formatdate, flt, getdate +from frappe.utils import cint, formatdate, flt, getdate from frappe import msgprint, _, throw from erpnext.setup.utils import get_company_currency import frappe.defaults @@ -53,6 +53,9 @@ class PurchaseInvoice(BuyingController): # validate stock items if (self.update_stock == 1): + pc_obj = frappe.get_doc('Purchase Common') + pc_obj.validate_for_items(self) + self.validate_purchase_return() self.validate_rejected_warehouse() self.validate_accepted_rejected_qty() @@ -318,43 +321,6 @@ class PurchaseInvoice(BuyingController): } ]) - def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): - sl_entries = [] - stock_items = self.get_stock_items() - - for d in self.get('items'): - if d.item_code in stock_items and d.warehouse: - pr_qty = flt(d.qty) * flt(d.conversion_factor) - - if pr_qty: - val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9 - rate = flt(d.valuation_rate, val_rate_db_precision) - sle = self.get_sl_entries(d, { - "actual_qty": flt(pr_qty), - "serial_no": cstr(d.serial_no).strip() - }) - if self.is_return: - sle.update({ - "outgoing_rate": rate - }) - else: - sle.update({ - "incoming_rate": rate - }) - sl_entries.append(sle) - - if flt(d.rejected_qty) > 0: - sl_entries.append(self.get_sl_entries(d, { - "warehouse": d.rejected_warehouse, - "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), - "serial_no": cstr(d.rejected_serial_no).strip(), - "incoming_rate": 0.0 - })) - - # self.bk_flush_supp_wh(sl_entries) - self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, - via_landed_cost_voucher=via_landed_cost_voucher) - def on_submit(self): self.check_prev_docstatus() self.validate_asset() diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 54053735d3..f58fd44902 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -11,6 +11,7 @@ import frappe.defaults from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ test_records as pr_test_records from erpnext.exceptions import InvalidCurrency +from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction test_dependencies = ["Item", "Cost Center"] test_ignore = ["Serial No"] @@ -317,6 +318,7 @@ class TestPurchaseInvoice(unittest.TestCase): def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self): set_perpetual_inventory() + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), posting_time=frappe.utils.nowtime()) @@ -359,6 +361,22 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEquals(expected_gl_entries[gle.account][0], gle.account) self.assertEquals(expected_gl_entries[gle.account][1], gle.debit) self.assertEquals(expected_gl_entries[gle.account][2], gle.credit) + + def test_update_stock_and_purchase_return(self): + actual_qty_0 = get_qty_after_transaction() + + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), + posting_time=frappe.utils.nowtime()) + + actual_qty_1 = get_qty_after_transaction() + self.assertEquals(actual_qty_0 + 5, actual_qty_1) + + # return entry + pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1) + + actual_qty_2 = get_qty_after_transaction() + + self.assertEquals(actual_qty_1 - 2, actual_qty_2) def make_purchase_invoice(**args): pi = frappe.new_doc("Purchase Invoice") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cc64a26aa8..cf93e5ba4a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -68,7 +68,7 @@ class AccountsController(TransactionBase): else: # show message that the amount is not paid frappe.db.set(self,'paid_amount',0) - frappe.msgprint(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) + frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) else: frappe.db.set(self,'paid_amount',0) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6a1b205057..abd176488f 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _, msgprint -from frappe.utils import flt +from frappe.utils import flt,cint, cstr from erpnext.setup.utils import get_company_currency from erpnext.accounts.party import get_party_details @@ -261,3 +261,52 @@ class BuyingController(StockController): if not d.conversion_factor: frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) d.stock_qty = flt(d.qty) * flt(d.conversion_factor) + + def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): + sl_entries = [] + stock_items = self.get_stock_items() + + for d in self.get('items'): + if d.item_code in stock_items and d.warehouse: + pr_qty = flt(d.qty) * flt(d.conversion_factor) + + if pr_qty: + val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9 + rate = flt(d.valuation_rate, val_rate_db_precision) + sle = self.get_sl_entries(d, { + "actual_qty": flt(pr_qty), + "serial_no": cstr(d.serial_no).strip() + }) + if self.is_return: + sle.update({ + "outgoing_rate": rate + }) + else: + sle.update({ + "incoming_rate": rate + }) + sl_entries.append(sle) + + if flt(d.rejected_qty) > 0: + sl_entries.append(self.get_sl_entries(d, { + "warehouse": d.rejected_warehouse, + "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), + "serial_no": cstr(d.rejected_serial_no).strip(), + "incoming_rate": 0.0 + })) + + self.make_sl_entries_for_supplier_warehouse(sl_entries) + self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, + via_landed_cost_voucher=via_landed_cost_voucher) + + def make_sl_entries_for_supplier_warehouse(self, sl_entries): + if hasattr(self, 'supplied_items'): + for d in self.get('supplied_items'): + # negative quantity is passed, as raw material qty has to be decreased + # when PR is submitted and it has to be increased when PR is cancelled + sl_entries.append(self.get_sl_entries(d, { + "item_code": d.rm_item_code, + "warehouse": self.supplier_warehouse, + "actual_qty": -1*flt(d.consumed_qty), + })) + diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 8ee6de9d0e..d16dd3988b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -130,43 +130,6 @@ class PurchaseReceipt(BuyingController): if not d.prevdoc_docname: frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) - def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): - sl_entries = [] - stock_items = self.get_stock_items() - - for d in self.get('items'): - if d.item_code in stock_items and d.warehouse: - pr_qty = flt(d.qty) * flt(d.conversion_factor) - - if pr_qty: - val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9 - rate = flt(d.valuation_rate, val_rate_db_precision) - sle = self.get_sl_entries(d, { - "actual_qty": flt(pr_qty), - "serial_no": cstr(d.serial_no).strip() - }) - if self.is_return: - sle.update({ - "outgoing_rate": rate - }) - else: - sle.update({ - "incoming_rate": rate - }) - sl_entries.append(sle) - - if flt(d.rejected_qty) > 0: - sl_entries.append(self.get_sl_entries(d, { - "warehouse": d.rejected_warehouse, - "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), - "serial_no": cstr(d.rejected_serial_no).strip(), - "incoming_rate": 0.0 - })) - - self.bk_flush_supp_wh(sl_entries) - self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, - via_landed_cost_voucher=via_landed_cost_voucher) - def update_ordered_qty(self): po_map = {} for d in self.get("items"): @@ -195,16 +158,6 @@ class PurchaseReceipt(BuyingController): ["qty", "warehouse"]) return po_qty, po_warehouse - def bk_flush_supp_wh(self, sl_entries): - for d in self.get('supplied_items'): - # negative quantity is passed as raw material qty has to be decreased - # when PR is submitted and it has to be increased when PR is cancelled - sl_entries.append(self.get_sl_entries(d, { - "item_code": d.rm_item_code, - "warehouse": self.supplier_warehouse, - "actual_qty": -1*flt(d.consumed_qty), - })) - def validate_inspection(self): for d in self.get('items'): #Enter inspection date for all items that require inspection if frappe.db.get_value("Item", d.item_code, "inspection_required") and not d.qa_no: From 130c57b20152ad676be273d8d412f48bf8a9ebd6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 4 Apr 2016 15:49:36 +0530 Subject: [PATCH 10/15] [fixes] code clean-up --- .../purchase_invoice/purchase_invoice.js | 4 -- .../purchase_invoice/purchase_invoice.py | 42 +------------------ .../purchase_common/purchase_common.js | 6 ++- erpnext/controllers/buying_controller.py | 30 +++++++++++++ .../purchase_receipt/purchase_receipt.js | 7 ---- .../purchase_receipt/purchase_receipt.py | 31 -------------- 6 files changed, 37 insertions(+), 83 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 03d0180105..6d137a5e12 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -136,10 +136,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ this.frm.refresh_fields(); }, - tc_name: function() { - this.get_terms(); - }, - items_add: function(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); this.frm.script_manager.copy_from_first_row("items", row, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 14f157ecd6..3cdb0e7005 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -53,18 +53,11 @@ class PurchaseInvoice(BuyingController): # validate stock items if (self.update_stock == 1): - pc_obj = frappe.get_doc('Purchase Common') - pc_obj.validate_for_items(self) - self.validate_purchase_return() self.validate_rejected_warehouse() self.validate_accepted_rejected_qty() - - # sub-contracting - # self.validate_for_subcontracting() - # self.create_raw_materials_supplied("supplied_items") - # self.set_landed_cost_voucher_amount() - # self.update_valuation_rate("items") + pc_obj = frappe.get_doc('Purchase Common') + pc_obj.validate_for_items(self) self.check_active_purchase_items() self.check_conversion_rate() @@ -88,37 +81,6 @@ class PurchaseInvoice(BuyingController): - flt(self.base_grand_total) > 1/(10**(self.precision("base_grand_total") + 1)): frappe.throw(_("""Paid amount + Write Off Amount can not be greater than Grand Total""")) - 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): - for d in self.get("items"): - if flt(d.rejected_qty) and not d.rejected_warehouse: - d.rejected_warehouse = self.rejected_warehouse - if not d.rejected_warehouse: - frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code)) - - # validate accepted and rejected qty - def validate_accepted_rejected_qty(self): - for d in self.get("items"): - if not flt(d.received_qty) and flt(d.qty): - d.received_qty = flt(d.qty) - flt(d.rejected_qty) - - elif not flt(d.qty) and flt(d.rejected_qty): - d.qty = flt(d.received_qty) - flt(d.rejected_qty) - - elif not flt(d.rejected_qty): - d.rejected_qty = flt(d.received_qty) - flt(d.qty) - - # Check Received Qty = Accepted Qty + Rejected Qty - if ((flt(d.qty) + flt(d.rejected_qty)) != flt(d.received_qty)): - frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) - - def create_remarks(self): if not self.remarks: if self.bill_no and self.bill_date: diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.js b/erpnext/buying/doctype/purchase_common/purchase_common.js index 5dcb0122f8..79eaeeb3cc 100644 --- a/erpnext/buying/doctype/purchase_common/purchase_common.js +++ b/erpnext/buying/doctype/purchase_common/purchase_common.js @@ -117,7 +117,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) { var item = frappe.get_doc(cdt, cdn); frappe.model.round_floats_in(item, ["qty", "received_qty"]); - if(!(item.received_qty || item.rejected_qty) && item.qty) { item.received_qty = item.qty; } @@ -153,6 +152,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ __(frappe.meta.get_label(item.doctype, "received_qty", item.name))])); item.qty = item.rejected_qty = 0.0; } else { + item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); } @@ -235,6 +235,10 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ erpnext.utils.get_address_display(this.frm, "shipping_address", "shipping_address_display", is_your_company_address=true) + }, + + tc_name: function() { + this.get_terms(); } }); diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index abd176488f..1bb968cb3b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -262,6 +262,36 @@ class BuyingController(StockController): frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) d.stock_qty = flt(d.qty) * flt(d.conversion_factor) + 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): + for d in self.get("items"): + if flt(d.rejected_qty) and not d.rejected_warehouse: + d.rejected_warehouse = self.rejected_warehouse + if not d.rejected_warehouse: + frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code)) + + # validate accepted and rejected qty + def validate_accepted_rejected_qty(self): + for d in self.get("items"): + if not flt(d.received_qty) and flt(d.qty): + d.received_qty = flt(d.qty) - flt(d.rejected_qty) + + elif not flt(d.qty) and flt(d.rejected_qty): + d.qty = flt(d.received_qty) - flt(d.rejected_qty) + + elif not flt(d.rejected_qty): + d.rejected_qty = flt(d.received_qty) - flt(d.qty) + + # Check Received Qty = Accepted Qty + Rejected Qty + if ((flt(d.qty) + flt(d.rejected_qty)) != flt(d.received_qty)): + frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) + def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): sl_entries = [] stock_items = self.get_stock_items() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index c2bbfbf64f..332dc63c68 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -24,7 +24,6 @@ frappe.ui.form.on("Purchase Receipt", { } }); - erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend({ refresh: function() { this._super(); @@ -90,10 +89,6 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend }) }, - tc_name: function() { - this.get_terms(); - }, - close_purchase_receipt: function() { cur_frm.cscript.update_status("Closed"); }, @@ -200,8 +195,6 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) { cur_frm.email_doc(frappe.boot.notification_settings.purchase_receipt_message); } - - frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d16dd3988b..1bdbc15f75 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -77,37 +77,6 @@ class PurchaseReceipt(BuyingController): 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): - for d in self.get("items"): - if flt(d.rejected_qty) and not d.rejected_warehouse: - d.rejected_warehouse = self.rejected_warehouse - if not d.rejected_warehouse: - frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code)) - - # validate accepted and rejected qty - def validate_accepted_rejected_qty(self): - for d in self.get("items"): - if not flt(d.received_qty) and flt(d.qty): - d.received_qty = flt(d.qty) - flt(d.rejected_qty) - - elif not flt(d.qty) and flt(d.rejected_qty): - d.qty = flt(d.received_qty) - flt(d.rejected_qty) - - elif not flt(d.rejected_qty): - d.rejected_qty = flt(d.received_qty) - flt(d.qty) - - # Check Received Qty = Accepted Qty + Rejected Qty - if ((flt(d.qty) + flt(d.rejected_qty)) != flt(d.received_qty)): - frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) - - def validate_with_previous_doc(self): super(PurchaseReceipt, self).validate_with_previous_doc({ "Purchase Order": { From fbb342c1ac4d6ba3dd9af2d8254b75b483f1b86f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 7 Apr 2016 16:41:31 +0530 Subject: [PATCH 11/15] [enhancement] update landed cost on PI via landed cost voucher when update stock is set on PI --- .../purchase_invoice/purchase_invoice.json | 135 +++++++++++++++++- .../purchase_invoice/purchase_invoice.py | 25 ++-- .../purchase_invoice_item.json | 58 +++++++- erpnext/controllers/buying_controller.py | 40 +++++- .../landed_cost_item/landed_cost_item.json | 59 +++++++- .../landed_cost_purchase_receipt.json | 50 ++++++- .../landed_cost_voucher.js | 26 ++-- .../landed_cost_voucher.py | 58 ++++---- .../purchase_receipt/purchase_receipt.py | 18 --- 9 files changed, 379 insertions(+), 90 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 46843b3f34..d47bedb45c 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -2178,6 +2178,112 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "raw_materials_supplied", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Raw Materials Supplied", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "No", + "fieldname": "is_subcontracted", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Raw Materials Supplied", + "length": 0, + "no_copy": 0, + "options": "No\nYes", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "supplier_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Supplier Warehouse", + "length": 0, + "no_copy": 1, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "print_width": "50px", + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "50px" + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "supplied_items", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Supplied Items", + "length": 0, + "no_copy": 0, + "options": "Purchase Receipt Item Supplied", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -2493,6 +2599,33 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "description": "Warehouse where you are maintaining stock of rejected items", + "fieldname": "rejected_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Rejected Warehouse", + "length": 0, + "no_copy": 1, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -2986,4 +3119,4 @@ "timeline_field": "supplier", "title_field": "title", "track_seen": 0 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3cdb0e7005..a8a0d84191 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -51,14 +51,6 @@ class PurchaseInvoice(BuyingController): if (self.is_paid == 1): self.validate_cash() - # validate stock items - if (self.update_stock == 1): - self.validate_purchase_return() - self.validate_rejected_warehouse() - self.validate_accepted_rejected_qty() - pc_obj = frappe.get_doc('Purchase Common') - pc_obj.validate_for_items(self) - self.check_active_purchase_items() self.check_conversion_rate() self.validate_credit_to_acc() @@ -279,9 +271,15 @@ class PurchaseInvoice(BuyingController): 'source_field': '-1 * qty', # 'percent_join_field': 'prevdoc_docname', # 'overflow_type': 'receipt', - 'extra_cond': """ and exists (select name from `tabPurchase Invoice` where name=`tabPurchase Invoice Item`.parent and is_return=1)""" + 'extra_cond': """ and exists (select name from `tabPurchase Invoice` + where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)""" } ]) + + def validate_purchase_receipt(self): + for item in self.get("items"): + if item.purchase_receipt: + frappe.throw(_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)) def on_submit(self): self.check_prev_docstatus() @@ -290,7 +288,6 @@ class PurchaseInvoice(BuyingController): frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) - # make purchase receipt if (self.update_stock == 1): # from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_stock_ledger self.update_stock_ledger() @@ -427,9 +424,15 @@ class PurchaseInvoice(BuyingController): for item in self.get("items"): if flt(item.base_net_amount): account_currency = get_account_currency(item.expense_account) + + if auto_accounting_for_stock and self.update_stock: + expense_account = warehouse_account[item.warehouse]["name"] + else: + expense_account = item.expense_account + gl_entries.append( self.get_gl_dict({ - "account": item.expense_account if not self.update_stock else warehouse_account[item.warehouse]["name"], + "account": expense_account, "against": self.supplier, "debit": item.base_net_amount, "debit_in_account_currency": item.base_net_amount \ diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 28b84ebbcd..f9f76605f0 100755 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -1282,6 +1282,32 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "bom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "BOM", + "length": 0, + "no_copy": 0, + "options": "BOM", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1435,7 +1461,7 @@ "unique": 0 }, { - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "fieldname": "valuation_rate", @@ -1483,6 +1509,31 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "fieldname": "landed_cost_voucher_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Landed Cost Voucher Amount", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -1494,7 +1545,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-03-28 05:05:27.752823", + "modified": "2016-04-07 16:38:30.502386", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -1503,5 +1554,6 @@ "read_only": 0, "read_only_onload": 0, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 1bb968cb3b..9c9f1c3ac8 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -32,6 +32,23 @@ class BuyingController(StockController): self.set_qty_as_per_stock_uom() self.validate_stock_or_nonstock_items() self.validate_warehouse() + + if self.doctype=="Purchase Invoice" and getattr(self, "update_stock"): + self.validate_purchase_receipt() + + if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and getattr(self, "update_stock")): + self.validate_purchase_return() + self.validate_rejected_warehouse() + self.validate_accepted_rejected_qty() + + pc_obj = frappe.get_doc('Purchase Common') + pc_obj.validate_for_items(self) + + #sub-contracting + self.validate_for_subcontracting() + self.create_raw_materials_supplied("supplied_items") + self.set_landed_cost_voucher_amount() + self.update_valuation_rate("items") def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) @@ -60,6 +77,13 @@ class BuyingController(StockController): if tax_for_valuation: frappe.throw(_("Tax Category can not be 'Valuation' or 'Valuation and Total' as all items are non-stock items")) + def set_landed_cost_voucher_amount(self): + for d in self.get("items"): + lc_voucher_amount = frappe.db.sql("""select sum(applicable_charges) + 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 set_total_in_words(self): from frappe.utils import money_in_words company_currency = get_company_currency(self.company) @@ -108,10 +132,10 @@ class BuyingController(StockController): item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0 qty_in_stock_uom = flt(item.qty * item.conversion_factor) - rm_supp_cost = flt(item.rm_supp_cost) if self.doctype=="Purchase Receipt" else 0.0 + rm_supp_cost = flt(item.rm_supp_cost) if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0 landed_cost_voucher_amount = flt(item.landed_cost_voucher_amount) \ - if self.doctype == "Purchase Receipt" else 0.0 + if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0 item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + rm_supp_cost + landed_cost_voucher_amount) / qty_in_stock_uom) @@ -123,7 +147,7 @@ class BuyingController(StockController): frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No")) if self.is_subcontracted == "Yes": - if self.doctype == "Purchase Receipt" and not self.supplier_warehouse: + if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse: frappe.throw(_("Supplier Warehouse mandatory for sub-contracted Purchase Receipt")) for item in self.get("items"): @@ -139,7 +163,7 @@ class BuyingController(StockController): if self.is_subcontracted=="Yes": parent_items = [] for item in self.get("items"): - if self.doctype == "Purchase Receipt": + if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: item.rm_supp_cost = 0.0 if item.item_code in self.sub_contracted_items: self.update_raw_materials_supplied(item, raw_material_table) @@ -149,7 +173,7 @@ class BuyingController(StockController): self.cleanup_raw_materials_supplied(parent_items, raw_material_table) - elif self.doctype == "Purchase Receipt": + elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]: for item in self.get("items"): item.rm_supp_cost = 0.0 @@ -179,7 +203,7 @@ class BuyingController(StockController): rm.conversion_factor = item.conversion_factor - if self.doctype == "Purchase Receipt": + if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: rm.consumed_qty = required_qty rm.description = bom_item.description if item.batch_no and not rm.batch_no: @@ -272,7 +296,9 @@ class BuyingController(StockController): def validate_rejected_warehouse(self): for d in self.get("items"): if flt(d.rejected_qty) and not d.rejected_warehouse: - d.rejected_warehouse = self.rejected_warehouse + if self.rejected_warehouse: + d.rejected_warehouse = self.rejected_warehouse + if not d.rejected_warehouse: frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code)) diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json index 9c1a74f3d8..fafcc84fb5 100644 --- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json +++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json @@ -6,6 +6,7 @@ "custom": 0, "docstatus": 0, "doctype": "DocType", + "document_type": "Document", "fields": [ { "allow_on_submit": 0, @@ -15,6 +16,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Item Code", @@ -23,6 +25,7 @@ "options": "Item", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 1, @@ -39,6 +42,7 @@ "fieldtype": "Text Editor", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Description", @@ -48,6 +52,7 @@ "oldfieldtype": "Data", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "print_width": "300px", "read_only": 1, "report_hide": 0, @@ -61,18 +66,47 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "purchase_receipt", - "fieldtype": "Link", + "fieldname": "receipt_document_type", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Purchase Receipt", + "label": "Receipt Document Type", "length": 0, "no_copy": 1, - "options": "Purchase Receipt", + "options": "Purchase Invoice\nPurchase Receipt", "permlevel": 0, + "precision": "", "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "receipt_document", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Receipt Document", + "length": 0, + "no_copy": 1, + "options": "receipt_document_type", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -88,12 +122,14 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -109,6 +145,7 @@ "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Qty", @@ -116,6 +153,7 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -131,6 +169,7 @@ "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Rate", @@ -139,6 +178,7 @@ "options": "Company:company:default_currency", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -154,6 +194,7 @@ "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Amount", @@ -164,6 +205,7 @@ "options": "Company:company:default_currency", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 1, @@ -179,6 +221,7 @@ "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Applicable Charges", @@ -187,6 +230,7 @@ "options": "Company:company:default_currency", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -202,6 +246,7 @@ "fieldtype": "Data", "hidden": 1, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Purchase Receipt Item", @@ -209,6 +254,7 @@ "no_copy": 1, "permlevel": 0, "print_hide": 1, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -226,12 +272,13 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2015-11-16 06:29:49.057949", + "modified": "2016-04-07 16:18:00.859492", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Item", "owner": "wasim@webnotestech.com", "permissions": [], "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json index 165982f010..db3b89ed52 100644 --- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json +++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json @@ -6,25 +6,54 @@ "custom": 0, "docstatus": 0, "doctype": "DocType", + "document_type": "Document", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "purchase_receipt", - "fieldtype": "Link", + "fieldname": "receipt_document_type", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Receipt Document Type", + "length": 0, + "no_copy": 0, + "options": "\nPurchase Invoice\nPurchase Receipt", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "receipt_document", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, - "label": "Purchase Receipt", + "label": "Receipt Document", "length": 0, "no_copy": 0, "oldfieldname": "purchase_receipt_no", "oldfieldtype": "Link", - "options": "Purchase Receipt", + "options": "receipt_document_type", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "print_width": "220px", "read_only": 0, "report_hide": 0, @@ -42,6 +71,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Supplier", @@ -50,6 +80,7 @@ "options": "Supplier", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -65,12 +96,14 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -87,6 +120,7 @@ "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Posting Date", @@ -94,6 +128,7 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -109,6 +144,7 @@ "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Grand Total", @@ -116,6 +152,7 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -133,12 +170,13 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2015-11-16 06:29:49.106523", + "modified": "2016-04-07 15:14:56.955036", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Purchase Receipt", "owner": "wasim@webnotestech.com", "permissions": [], "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index 0bb8f9090d..5a86c89d55 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -8,20 +8,28 @@ frappe.require("assets/erpnext/js/controllers/stock_controller.js"); erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ setup: function() { var me = this; - this.frm.fields_dict.purchase_receipts.grid.get_field('purchase_receipt').get_query = - function() { + this.frm.fields_dict.purchase_receipts.grid.get_field('receipt_document').get_query = + function(doc, cdt ,cdn) { + var d = locals[cdt][cdn] + + var filters = [ + [d.receipt_document_type, 'docstatus', '=', '1'], + [d.receipt_document_type, 'company', '=', me.frm.doc.company], + ] + + if(d.receipt_document_type == "Purchase Invoice") { + filters.push(["Purchase Invoice", "update_stock", "=", "1"]) + } + if(!me.frm.doc.company) msgprint(__("Please enter company first")); return { - filters:[ - ['Purchase Receipt', 'docstatus', '=', '1'], - ['Purchase Receipt', 'company', '=', me.frm.doc.company], - ] + filters:filters } }; - this.frm.add_fetch("purchase_receipt", "supplier", "supplier"); - this.frm.add_fetch("purchase_receipt", "posting_date", "posting_date"); - this.frm.add_fetch("purchase_receipt", "base_grand_total", "grand_total"); + this.frm.add_fetch("receipt_document", "supplier", "supplier"); + this.frm.add_fetch("receipt_document", "posting_date", "posting_date"); + this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total"); }, diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 3418c9ef1d..ac59e06ca8 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -14,9 +14,9 @@ class LandedCostVoucher(Document): for pr in self.get("purchase_receipts"): pr_items = frappe.db.sql("""select pr_item.item_code, pr_item.description, pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name - from `tabPurchase Receipt Item` pr_item where parent = %s - and exists(select name from tabItem where name = pr_item.item_code and is_stock_item = 1)""", - pr.purchase_receipt, as_dict=True) + from `tab{doctype} Item` pr_item where parent = %s + and exists(select name from tabItem where name = pr_item.item_code and is_stock_item = 1) + """.format(doctype=pr.receipt_document_type), pr.receipt_document, as_dict=True) for d in pr_items: item = self.append("items") @@ -25,13 +25,13 @@ class LandedCostVoucher(Document): item.qty = d.qty item.rate = d.base_rate item.amount = d.base_amount - item.purchase_receipt = pr.purchase_receipt + item.receipt_document_type = pr.receipt_document_type + item.receipt_document = pr.receipt_document item.purchase_receipt_item = d.name if self.get("taxes"): self.set_applicable_charges_for_item() - def validate(self): self.check_mandatory() self.validate_purchase_receipts() @@ -43,25 +43,26 @@ class LandedCostVoucher(Document): def check_mandatory(self): if not self.get("purchase_receipts"): - frappe.throw(_("Please enter Purchase Receipts")) + frappe.throw(_("Please enter Receipt Document")) if not self.get("taxes"): frappe.throw(_("Please enter Taxes and Charges")) def validate_purchase_receipts(self): - purchase_receipts = [] + receipt_documents = [] + for d in self.get("purchase_receipts"): - if frappe.db.get_value("Purchase Receipt", d.purchase_receipt, "docstatus") != 1: - frappe.throw(_("Purchase Receipt must be submitted")) + if frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus") != 1: + frappe.throw(_("Receipt document must be submitted")) else: - purchase_receipts.append(d.purchase_receipt) + receipt_documents.append(d.receipt_document) for item in self.get("items"): - if not item.purchase_receipt: + if not item.receipt_document: frappe.throw(_("Item must be added using 'Get Items from Purchase Receipts' button")) - elif item.purchase_receipt not in purchase_receipts: - frappe.throw(_("Item Row {0}: Purchase Receipt {1} does not exist in above 'Purchase Receipts' table") - .format(item.idx, item.purchase_receipt)) + elif item.receipt_document not in receipt_documents: + frappe.throw(_("Item Row {idx}: {doctype} {docname} does not exist in above '{doctype}' table") + .format(idx=item.idx, doctype=item.receipt_document_type, docname=item.receipt_document)) def set_total_taxes_and_charges(self): self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")]) @@ -83,36 +84,35 @@ class LandedCostVoucher(Document): self.update_landed_cost() def update_landed_cost(self): - purchase_receipts = list(set([d.purchase_receipt for d in self.get("items")])) - for purchase_receipt in purchase_receipts: - pr = frappe.get_doc("Purchase Receipt", purchase_receipt) + for d in self.get("items"): + doc = frappe.get_doc(d.receipt_document_type, d.receipt_document) # set landed cost voucher amount in pr item - pr.set_landed_cost_voucher_amount() + doc.set_landed_cost_voucher_amount() # set valuation amount in pr item - pr.update_valuation_rate("items") + doc.update_valuation_rate("items") # save will update landed_cost_voucher_amount and voucher_amount in PR, # as those fields are allowed to edit after submit - pr.save() + doc.save() # update latest valuation rate in serial no - self.update_rate_in_serial_no(pr) + self.update_rate_in_serial_no(doc) # update stock & gl entries for cancelled state of PR - pr.docstatus = 2 - pr.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) - pr.make_gl_entries_on_cancel() + doc.docstatus = 2 + doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) + doc.make_gl_entries_on_cancel() # update stock & gl entries for submit state of PR - pr.docstatus = 1 - pr.update_stock_ledger(via_landed_cost_voucher=True) - pr.make_gl_entries() + doc.docstatus = 1 + doc.update_stock_ledger(via_landed_cost_voucher=True) + doc.make_gl_entries() - def update_rate_in_serial_no(self, purchase_receipt): - for item in purchase_receipt.get("items"): + def update_rate_in_serial_no(self, receipt_document): + for item in receipt_document.get("items"): if item.serial_no: serial_nos = get_serial_nos(item.serial_no) if serial_nos: diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1bdbc15f75..bad1228dd2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -52,31 +52,13 @@ class PurchaseReceipt(BuyingController): self.set_status() self.po_required() self.validate_with_previous_doc() - self.validate_purchase_return() - self.validate_rejected_warehouse() - self.validate_accepted_rejected_qty() self.validate_inspection() self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("stock_uom", "stock_qty") pc_obj = frappe.get_doc('Purchase Common') - pc_obj.validate_for_items(self) self.check_for_closed_status(pc_obj) - # sub-contracting - self.validate_for_subcontracting() - self.create_raw_materials_supplied("supplied_items") - self.set_landed_cost_voucher_amount() - self.update_valuation_rate("items") - - - def set_landed_cost_voucher_amount(self): - for d in self.get("items"): - lc_voucher_amount = frappe.db.sql("""select sum(applicable_charges) - 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_with_previous_doc(self): super(PurchaseReceipt, self).validate_with_previous_doc({ "Purchase Order": { From 224737b9ed38b2423eb298771451e7e6704bc37e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 11 Apr 2016 14:43:30 +0530 Subject: [PATCH 12/15] [fixes] split make gl entry function into chuncks as per SI --- .../purchase_invoice/purchase_invoice.json | 20 +- .../purchase_invoice/purchase_invoice.py | 268 ++++++++++-------- erpnext/accounts/general_ledger.py | 2 +- .../test_landed_cost_voucher.py | 3 +- 4 files changed, 157 insertions(+), 136 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d47bedb45c..90a0053331 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1718,7 +1718,7 @@ "bold": 0, "collapsible": 1, "collapsible_depends_on": "paid_amount", - "depends_on": "eval:doc.is_paid===1", + "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", "hidden": 0, @@ -1754,11 +1754,10 @@ "label": "Mode of Payment", "length": 0, "no_copy": 0, - "oldfieldname": "mode_of_payment", - "oldfieldtype": "Select", "options": "Mode of Payment", "permlevel": 0, - "print_hide": 0, + "precision": "", + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, @@ -1821,6 +1820,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "depends_on": "is_paid", "fieldname": "paid_amount", "fieldtype": "Currency", "hidden": 0, @@ -1830,11 +1830,11 @@ "in_list_view": 0, "label": "Paid Amount", "length": 0, - "no_copy": 0, + "no_copy": 1, "options": "currency", "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, @@ -1856,13 +1856,13 @@ "in_list_view": 0, "label": "Paid Amount (Company Currency)", "length": 0, - "no_copy": 0, + "no_copy": 1, "options": "Company:company:default_currency", "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -3004,7 +3004,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2016-04-06 05:39:45.475873", + "modified": "2016-04-11 14:37:27.243253", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a8a0d84191..8c4db50458 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -335,14 +335,153 @@ class PurchaseInvoice(BuyingController): frappe.db.set_value("Asset", asset.name, "supplier", self.supplier) def make_gl_entries(self): - auto_accounting_for_stock = \ + self.auto_accounting_for_stock = \ cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) - stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - + self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") + self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + self.negative_expense_to_be_booked = 0.0 gl_entries = [] + + + self.make_supplier_gl_entry(gl_entries) + self.make_item_gl_entries(gl_entries) + self.make_tax_gl_entries(gl_entries) + + from erpnext.accounts.general_ledger import merge_similar_entries + gl_entries = merge_similar_entries(gl_entries) + + self.make_payment_gl_entries(gl_entries) + self.make_write_off_gl_entry(gl_entries) + if gl_entries: + from erpnext.accounts.general_ledger import make_gl_entries + update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" + + make_gl_entries(gl_entries, cancel=(self.docstatus == 2), + update_outstanding=update_outstanding, merge_entries=False) + + def make_supplier_gl_entry(self, gl_entries): + # parent's gl entry + if self.grand_total: + # Didnot use base_grand_total to book rounding loss gle + grand_total_in_company_currency = flt(self.grand_total * self.conversion_rate, + self.precision("grand_total")) + gl_entries.append( + self.get_gl_dict({ + "account": self.credit_to, + "party_type": "Supplier", + "party": self.supplier, + "against": self.against_expense_account, + "credit": grand_total_in_company_currency, + "credit_in_account_currency": grand_total_in_company_currency \ + if self.party_account_currency==self.company_currency else self.grand_total, + "against_voucher": self.return_against if cint(self.is_return) else self.name, + "against_voucher_type": self.doctype, + }, self.party_account_currency) + ) + + def make_item_gl_entries(self, gl_entries): + # item gl entries + stock_items = self.get_stock_items() + warehouse_account = get_warehouse_account() + + for item in self.get("items"): + if flt(item.base_net_amount): + account_currency = get_account_currency(item.expense_account) + + if self.auto_accounting_for_stock and self.update_stock: + expense_account = warehouse_account[item.warehouse]["name"] + else: + expense_account = item.expense_account + + gl_entries.append( + self.get_gl_dict({ + "account": expense_account, + "against": self.supplier, + "debit": item.base_net_amount, + "debit_in_account_currency": item.base_net_amount \ + if account_currency==self.company_currency else item.net_amount, + "cost_center": item.cost_center + }, account_currency) + ) + + if self.auto_accounting_for_stock and self.is_opening == "No" and \ + item.item_code in stock_items and item.item_tax_amount: + # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt + if item.purchase_receipt: + negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry` + where voucher_type='Purchase Receipt' and voucher_no=%s and account=%s""", + (item.purchase_receipt, self.expenses_included_in_valuation)) + + if not negative_expense_booked_in_pr: + gl_entries.append( + self.get_gl_dict({ + "account": self.stock_received_but_not_billed, + "against": self.supplier, + "debit": flt(item.item_tax_amount, self.precision("item_tax_amount", item)), + "remarks": self.remarks or "Accounting Entry for Stock" + }) + ) + + self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ + self.precision("item_tax_amount", item)) + + def make_tax_gl_entries(self, gl_entries): + # tax table gl entries + valuation_tax = {} + for tax in self.get("taxes"): + if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): + account_currency = get_account_currency(tax.account_head) + + dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "against": self.supplier, + dr_or_cr: tax.base_tax_amount_after_discount_amount, + dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ + if account_currency==self.company_currency \ + else tax.tax_amount_after_discount_amount, + "cost_center": tax.cost_center + }, account_currency) + ) + # accumulate valuation tax + if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): + if self.auto_accounting_for_stock and not tax.cost_center: + frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) + valuation_tax.setdefault(tax.cost_center, 0) + valuation_tax[tax.cost_center] += \ + (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) + + if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax: + # credit valuation tax amount in "Expenses Included In Valuation" + # this will balance out valuation amount included in cost of goods sold + + total_valuation_amount = sum(valuation_tax.values()) + amount_including_divisional_loss = self.negative_expense_to_be_booked + i = 1 + for cost_center, amount in valuation_tax.items(): + if i == len(valuation_tax): + applicable_amount = amount_including_divisional_loss + else: + applicable_amount = self.negative_expense_to_be_booked * (amount / total_valuation_amount) + amount_including_divisional_loss -= applicable_amount + + gl_entries.append( + self.get_gl_dict({ + "account": self.expenses_included_in_valuation, + "cost_center": cost_center, + "against": self.supplier, + "credit": applicable_amount, + "remarks": self.remarks or "Accounting Entry for Stock" + }) + ) + + i += 1 + + def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries if cint(self.is_paid) and self.cash_bank_account and self.paid_amount: bank_account_currency = get_account_currency(self.cash_bank_account) @@ -370,123 +509,8 @@ class PurchaseInvoice(BuyingController): if bank_account_currency==self.company_currency else self.paid_amount }, bank_account_currency) ) - - # parent's gl entry - if self.grand_total: - # Didnot use base_grand_total to book rounding loss gle - grand_total_in_company_currency = flt(self.grand_total * self.conversion_rate, - self.precision("grand_total")) - - gl_entries.append( - self.get_gl_dict({ - "account": self.credit_to, - "party_type": "Supplier", - "party": self.supplier, - "against": self.against_expense_account, - "credit": grand_total_in_company_currency, - "credit_in_account_currency": grand_total_in_company_currency \ - if self.party_account_currency==self.company_currency else self.grand_total, - "against_voucher": self.return_against if cint(self.is_return) else self.name, - "against_voucher_type": self.doctype, - }, self.party_account_currency) - ) - # tax table gl entries - valuation_tax = {} - for tax in self.get("taxes"): - if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): - account_currency = get_account_currency(tax.account_head) - - dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - - gl_entries.append( - self.get_gl_dict({ - "account": tax.account_head, - "against": self.supplier, - dr_or_cr: tax.base_tax_amount_after_discount_amount, - dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ - if account_currency==self.company_currency \ - else tax.tax_amount_after_discount_amount, - "cost_center": tax.cost_center - }, account_currency) - ) - # accumulate valuation tax - if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): - if auto_accounting_for_stock and not tax.cost_center: - frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) - valuation_tax.setdefault(tax.cost_center, 0) - valuation_tax[tax.cost_center] += \ - (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) - # item gl entries - negative_expense_to_be_booked = 0.0 - stock_items = self.get_stock_items() - warehouse_account = get_warehouse_account() - - for item in self.get("items"): - if flt(item.base_net_amount): - account_currency = get_account_currency(item.expense_account) - - if auto_accounting_for_stock and self.update_stock: - expense_account = warehouse_account[item.warehouse]["name"] - else: - expense_account = item.expense_account - - gl_entries.append( - self.get_gl_dict({ - "account": expense_account, - "against": self.supplier, - "debit": item.base_net_amount, - "debit_in_account_currency": item.base_net_amount \ - if account_currency==self.company_currency else item.net_amount, - "cost_center": item.cost_center - }, account_currency) - ) - - if auto_accounting_for_stock and self.is_opening == "No" and \ - item.item_code in stock_items and item.item_tax_amount: - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - if item.purchase_receipt: - negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no=%s and account=%s""", - (item.purchase_receipt, expenses_included_in_valuation)) - - if not negative_expense_booked_in_pr: - gl_entries.append( - self.get_gl_dict({ - "account": stock_received_but_not_billed, - "against": self.supplier, - "debit": flt(item.item_tax_amount, self.precision("item_tax_amount", item)), - "remarks": self.remarks or "Accounting Entry for Stock" - }) - ) - - negative_expense_to_be_booked += flt(item.item_tax_amount, self.precision("item_tax_amount", item)) - - if self.is_opening == "No" and negative_expense_to_be_booked and valuation_tax: - # credit valuation tax amount in "Expenses Included In Valuation" - # this will balance out valuation amount included in cost of goods sold - - total_valuation_amount = sum(valuation_tax.values()) - amount_including_divisional_loss = negative_expense_to_be_booked - i = 1 - for cost_center, amount in valuation_tax.items(): - if i == len(valuation_tax): - applicable_amount = amount_including_divisional_loss - else: - applicable_amount = negative_expense_to_be_booked * (amount / total_valuation_amount) - amount_including_divisional_loss -= applicable_amount - - gl_entries.append( - self.get_gl_dict({ - "account": expenses_included_in_valuation, - "cost_center": cost_center, - "against": self.supplier, - "credit": applicable_amount, - "remarks": self.remarks or "Accounting Entry for Stock" - }) - ) - - i += 1 + def make_write_off_gl_entry(self, gl_entries): # writeoff account includes petty difference in the invoice amount # and the amount that is paid if self.write_off_account and flt(self.write_off_amount): @@ -515,10 +539,6 @@ class PurchaseInvoice(BuyingController): "cost_center": self.write_off_cost_center }) ) - - if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries - make_gl_entries(gl_entries, cancel=(self.docstatus == 2)) def on_cancel(self): self.check_for_closed_status() diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 2063edf30e..cf9f4fc819 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -25,7 +25,7 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd def process_gl_map(gl_map, merge_entries=True): if merge_entries: gl_map = merge_similar_entries(gl_map) - + print gl_map for entry in gl_map: # toggle debit, credit if negative entry if flt(entry.debit) < 0: diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 7f190d2edc..8323460902 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -88,7 +88,8 @@ class TestLandedCostVoucher(unittest.TestCase): lcv = frappe.new_doc("Landed Cost Voucher") lcv.company = "_Test Company" lcv.set("purchase_receipts", [{ - "purchase_receipt": pr.name, + "receipt_document_type": "Purchase Receipt", + "receipt_document": pr.name, "supplier": pr.supplier, "posting_date": pr.posting_date, "grand_total": pr.base_grand_total From 7fee1b83439d223aa680bdcd479cbe92dbf8d037 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 12 Apr 2016 11:01:04 +0530 Subject: [PATCH 13/15] [fixes] set expence account to item on sales invoice --- .../purchase_invoice/purchase_invoice.py | 78 +++++++++++-------- .../purchase_invoice/test_purchase_invoice.py | 14 ++-- erpnext/accounts/general_ledger.py | 1 - .../test_landed_cost_voucher.py | 26 +++++-- 4 files changed, 75 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 8c4db50458..81d386717e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -58,6 +58,7 @@ class PurchaseInvoice(BuyingController): self.check_for_closed_status() self.validate_with_previous_doc() self.validate_uom_is_integer("uom", "qty") + self.set_expense_account() self.set_against_expense_account() self.validate_write_off_account() self.update_valuation_rate("items") @@ -150,52 +151,56 @@ class PurchaseInvoice(BuyingController): ["Purchase Order", "purchase_order", "po_detail"], ["Purchase Receipt", "purchase_receipt", "pr_detail"] ]) - - def set_against_expense_account(self): + + def set_expense_account(self): auto_accounting_for_stock = cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) if auto_accounting_for_stock: stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") - - against_accounts = [] - stock_items = self.get_stock_items() + stock_items = self.get_stock_items() + + if self.update_stock: + warehouse_account = get_warehouse_account() + for item in self.get("items"): # in case of auto inventory accounting, # expense account is always "Stock Received But Not Billed" for a stock item # except epening entry, drop-ship entry and fixed asset items - if auto_accounting_for_stock and item.item_code in stock_items and self.is_opening == 'No' \ - and (not item.po_detail - or not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier") - or not frappe.db.get_value("Item", item.item_code, "is_fixed_asset")): + if auto_accounting_for_stock and self.is_opening == 'No' \ + and item.item_code in stock_items and ((not item.po_detail + or not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")) + or not frappe.db.get_value("Item", item.item_code, "is_fixed_asset")): - item.expense_account = stock_not_billed_account + if self.update_stock: + item.expense_account = warehouse_account[item.warehouse]["name"] + else: + item.expense_account = stock_not_billed_account + item.cost_center = None - - if stock_not_billed_account not in against_accounts: - against_accounts.append(stock_not_billed_account) - elif not item.expense_account: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) - elif item.expense_account not in against_accounts: - # if no auto_accounting_for_stock or not a stock item + def set_against_expense_account(self): + against_accounts = [] + for item in self.get("items"): + if item.expense_account not in against_accounts: against_accounts.append(item.expense_account) self.against_expense_account = ",".join(against_accounts) def po_required(self): if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes': - for d in self.get('items'): - if not d.purchase_order: - throw(_("Purchse Order number required for Item {0}").format(d.item_code)) + for d in self.get('items'): + if not d.purchase_order: + throw(_("Purchse Order number required for Item {0}").format(d.item_code)) def pr_required(self): stock_items = self.get_stock_items() if frappe.db.get_value("Buying Settings", None, "pr_required") == 'Yes': - for d in self.get('items'): - if not d.purchase_receipt and d.item_code in stock_items: - throw(_("Purchase Receipt number required for Item {0}").format(d.item_code)) + for d in self.get('items'): + if not d.purchase_receipt and d.item_code in stock_items: + throw(_("Purchase Receipt number required for Item {0}").format(d.item_code)) def validate_write_off_account(self): if self.write_off_amount and not self.write_off_account: @@ -334,7 +339,7 @@ class PurchaseInvoice(BuyingController): if self.docstatus==1 and not asset.supplier: frappe.db.set_value("Asset", asset.name, "supplier", self.supplier) - def make_gl_entries(self): + def make_gl_entries(self, repost_future_gle=False): self.auto_accounting_for_stock = \ cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) @@ -360,6 +365,23 @@ class PurchaseInvoice(BuyingController): make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding=update_outstanding, merge_entries=False) + + if update_outstanding == "No": + from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt + update_outstanding_amt(self.credit_to, "Supplier", self.supplier, + self.doctype, self.return_against if cint(self.is_return) else self.name) + + if repost_future_gle and cint(self.update_stock) \ + and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): + from erpnext.controllers.stock_controller import update_gl_entries_after + items, warehouses = self.get_items_and_warehouses() + update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items) + + elif self.docstatus == 2 and cint(self.update_stock) \ + and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): + from erpnext.accounts.general_ledger import delete_gl_entries + delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + def make_supplier_gl_entry(self, gl_entries): # parent's gl entry @@ -384,20 +406,14 @@ class PurchaseInvoice(BuyingController): def make_item_gl_entries(self, gl_entries): # item gl entries stock_items = self.get_stock_items() - warehouse_account = get_warehouse_account() - + for item in self.get("items"): if flt(item.base_net_amount): account_currency = get_account_currency(item.expense_account) - if self.auto_accounting_for_stock and self.update_stock: - expense_account = warehouse_account[item.warehouse]["name"] - else: - expense_account = item.expense_account - gl_entries.append( self.get_gl_dict({ - "account": expense_account, + "account": item.expense_account, "against": self.supplier, "debit": item.base_net_amount, "debit_in_account_currency": item.base_net_amount \ diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index f58fd44902..c0691744a6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -344,19 +344,19 @@ class TestPurchaseInvoice(unittest.TestCase): pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1) - gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", pi.name, as_dict=1) + gl_entries = frappe.db.sql("""select account, account_currency, sum(debit) as debit, + sum(credit) as credit, debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + group by account, voucher_no order by account asc;""", pi.name, as_dict=1) self.assertTrue(gl_entries) expected_gl_entries = dict((d[0], d) for d in [ - [pi.credit_to, 250, 250.0], + [pi.credit_to, 250.0, 250.0], [pi.items[0].warehouse, 250.0, 0.0], ["Cash - _TC", 0.0, 250.0] ]) - + for i, gle in enumerate(gl_entries): self.assertEquals(expected_gl_entries[gle.account][0], gle.account) self.assertEquals(expected_gl_entries[gle.account][1], gle.debit) @@ -365,7 +365,7 @@ class TestPurchaseInvoice(unittest.TestCase): def test_update_stock_and_purchase_return(self): actual_qty_0 = get_qty_after_transaction() - pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), posting_time=frappe.utils.nowtime()) actual_qty_1 = get_qty_after_transaction() diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index cf9f4fc819..a8a090f9e8 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -25,7 +25,6 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd def process_gl_map(gl_map, merge_entries=True): if merge_entries: gl_map = merge_similar_entries(gl_map) - print gl_map for entry in gl_map: # toggle debit, credit if negative entry if flt(entry.debit) < 0: diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 8323460902..81708ce83d 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -7,13 +7,16 @@ import unittest import frappe from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ import set_perpetual_inventory, get_gl_entries, test_records as pr_test_records - +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher(self): set_perpetual_inventory(1) pr = frappe.copy_doc(pr_test_records[0]) pr.submit() + + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), + posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1) last_sle = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pr.doctype, @@ -24,10 +27,13 @@ class TestLandedCostVoucher(unittest.TestCase): fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - self.submit_landed_cost_voucher(pr) + self.submit_landed_cost_voucher(pr, pi) pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount") self.assertEquals(pr_lc_value, 25.0) + + pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, "landed_cost_voucher_amount") + self.assertEquals(pi_lc_value, 25.0) last_sle_after_landed_cost = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pr.doctype, @@ -79,12 +85,12 @@ class TestLandedCostVoucher(unittest.TestCase): serial_no = frappe.db.get_value("Serial No", "SN001", ["warehouse", "purchase_rate"], as_dict=1) - self.assertEquals(serial_no.purchase_rate - serial_no_rate, 5.0) + self.assertEquals(serial_no.purchase_rate - serial_no_rate, 7.5) self.assertEquals(serial_no.warehouse, "_Test Warehouse - _TC") set_perpetual_inventory(0) - def submit_landed_cost_voucher(self, pr): + def submit_landed_cost_voucher(self, pr, pi=None): lcv = frappe.new_doc("Landed Cost Voucher") lcv.company = "_Test Company" lcv.set("purchase_receipts", [{ @@ -94,10 +100,20 @@ class TestLandedCostVoucher(unittest.TestCase): "posting_date": pr.posting_date, "grand_total": pr.base_grand_total }]) + + if pi: + lcv.append("purchase_receipts", { + "receipt_document_type": "Purchase Invoice", + "receipt_document": pi.name, + "supplier": pi.supplier, + "posting_date": pi.posting_date, + "grand_total": pi.base_grand_total + }) + lcv.set("taxes", [{ "description": "Insurance Charges", "account": "_Test Account Insurance Charges - _TC", - "amount": 50.0 + "amount": 75.0 }]) lcv.insert() From ffd1e4efe6b6d481acc52fe9faeb95aab628cc62 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 15 Apr 2016 14:42:08 +0530 Subject: [PATCH 14/15] Removed db_set from inside validate --- erpnext/controllers/accounts_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cf93e5ba4a..7691ff5b5f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -63,11 +63,11 @@ class AccountsController(TransactionBase): if cint(is_paid) == 1: if flt(self.paid_amount) == 0: if self.cash_bank_account: - frappe.db.set(self, 'paid_amount', - flt(flt(self.grand_total) - flt(self.write_off_amount), self.precision("paid_amount"))) + self.paid_amount = flt(flt(self.grand_total) - flt(self.write_off_amount), + self.precision("paid_amount")) else: # show message that the amount is not paid - frappe.db.set(self,'paid_amount',0) + self.paid_amount = 0 frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) else: frappe.db.set(self,'paid_amount',0) From 14aa9c5320d46b8c1868eab3d527f27cafb8d32f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 18 Apr 2016 15:54:01 +0530 Subject: [PATCH 15/15] [fix] Cleanup and fixes in update stock feature in Purchase Invoice --- .../purchase_invoice/purchase_invoice.js | 42 +++++- .../purchase_invoice/purchase_invoice.py | 137 ++++++++++++------ .../purchase_invoice/test_purchase_invoice.py | 39 ++++- .../purchase_invoice/test_records.json | 6 +- .../doctype/sales_invoice/sales_invoice.py | 27 ++-- .../purchase_order/test_purchase_order.py | 26 ++++ erpnext/controllers/accounts_controller.py | 18 +-- erpnext/controllers/buying_controller.py | 35 ++++- erpnext/controllers/selling_controller.py | 74 ++++++++++ erpnext/controllers/stock_controller.py | 75 ---------- erpnext/patches.txt | 2 +- .../doctype/sales_order/test_sales_order.py | 28 ++++ .../doctype/delivery_note/delivery_note.py | 4 + .../test_landed_cost_voucher.py | 92 ++++++++---- .../purchase_receipt/purchase_receipt.js | 14 ++ .../purchase_receipt/purchase_receipt.py | 45 ++---- 16 files changed, 429 insertions(+), 235 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 6d137a5e12..5af3e82efc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -15,30 +15,30 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ this.frm.set_df_property("credit_to", "print_hide", 0); } } - - hide_fields(this.frm.doc); }, refresh: function(doc) { this._super(); + + hide_fields(this.frm.doc); // Show / Hide button this.show_general_ledger(); + + if(doc.update_stock==1 && doc.docstatus==1) { + this.show_stock_ledger(); + } if(!doc.is_return && doc.docstatus==1) { if(doc.outstanding_amount > 0) { this.frm.add_custom_button(__('Payment'), this.make_bank_entry, __("Make")); cur_frm.page.set_inner_btn_group_as_primary(__("Make")); } - + if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { cur_frm.add_custom_button(doc.update_stock ? __('Purchase Return') : __('Debit Note'), this.make_debit_note, __("Make")); } - - if(doc.update_stock==1) { - this.show_stock_ledger(); - } } if(doc.docstatus===0) { @@ -69,6 +69,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }) }, __("Get items from")); } + + this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes"); }, supplier: function() { @@ -326,3 +328,29 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){ else cur_frm.pformat.print_heading = __("Purchase Invoice"); } + +frappe.ui.form.on("Purchase Invoice", { + onload: function(frm) { + $.each(["warehouse", "rejected_warehouse"], function(i, field) { + frm.set_query(field, "items", function() { + return { + filters: [["Warehouse", "company", "in", ["", cstr(frm.doc.company)]]] + } + }) + }) + + frm.set_query("supplier_warehouse", function() { + return { + filters: [["Warehouse", "company", "in", ["", cstr(frm.doc.company)]]] + } + }) + }, + + is_subcontracted: function(frm) { + if (frm.doc.is_subcontracted === "Yes") { + erpnext.buying.get_default_bom(frm); + } + frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); + } +}) + \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 81d386717e..43d14edbb3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import cint, formatdate, flt, getdate -from frappe import msgprint, _, throw +from frappe import _, throw from erpnext.setup.utils import get_company_currency import frappe.defaults @@ -13,7 +13,8 @@ from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po from erpnext.controllers.stock_controller import get_warehouse_account - +from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries +from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -51,7 +52,6 @@ class PurchaseInvoice(BuyingController): if (self.is_paid == 1): self.validate_cash() - self.check_active_purchase_items() self.check_conversion_rate() self.validate_credit_to_acc() self.clear_unallocated_advances("Purchase Invoice Advance", "advances") @@ -61,7 +61,6 @@ class PurchaseInvoice(BuyingController): self.set_expense_account() self.set_against_expense_account() self.validate_write_off_account() - self.update_valuation_rate("items") self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items") self.validate_fixed_asset_account() self.create_remarks() @@ -177,7 +176,6 @@ class PurchaseInvoice(BuyingController): else: item.expense_account = stock_not_billed_account - item.cost_center = None elif not item.expense_account: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) @@ -281,35 +279,38 @@ class PurchaseInvoice(BuyingController): } ]) - def validate_purchase_receipt(self): - for item in self.get("items"): - if item.purchase_receipt: - frappe.throw(_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)) + def validate_purchase_receipt_if_update_stock(self): + if self.update_stock: + for item in self.get("items"): + if item.purchase_receipt: + frappe.throw(_("Stock cannot be updated against Purchase Receipt {0}") + .format(item.purchase_receipt)) def on_submit(self): self.check_prev_docstatus() + self.update_status_updater_args() + self.validate_asset() frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) - - if (self.update_stock == 1): - # from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_stock_ledger - self.update_stock_ledger() - from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit - update_serial_nos_after_submit(self, "items") - self.update_status_updater_args() - self.update_prevdoc_status() - - # this sequence because outstanding may get -negative - self.make_gl_entries() - + if not self.is_return: self.update_against_document_in_jv() self.update_prevdoc_status() self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_in_pr() + # Updating stock ledger should always be called after updating prevdoc status, + # because updating ordered qty in bin depends upon updated ordered qty in PO + if self.update_stock == 1: + self.update_stock_ledger() + from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit + update_serial_nos_after_submit(self, "items") + + # this sequence because outstanding may get -negative + self.make_gl_entries() + self.update_project() def validate_asset(self): @@ -353,38 +354,32 @@ class PurchaseInvoice(BuyingController): self.make_item_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries) - from erpnext.accounts.general_ledger import merge_similar_entries gl_entries = merge_similar_entries(gl_entries) self.make_payment_gl_entries(gl_entries) self.make_write_off_gl_entry(gl_entries) + if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding=update_outstanding, merge_entries=False) if update_outstanding == "No": - from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt update_outstanding_amt(self.credit_to, "Supplier", self.supplier, self.doctype, self.return_against if cint(self.is_return) else self.name) - if repost_future_gle and cint(self.update_stock) \ - and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): - from erpnext.controllers.stock_controller import update_gl_entries_after - items, warehouses = self.get_items_and_warehouses() - update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items) + if repost_future_gle and cint(self.update_stock) and self.auto_accounting_for_stock: + from erpnext.controllers.stock_controller import update_gl_entries_after + items, warehouses = self.get_items_and_warehouses() + update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items) - elif self.docstatus == 2 and cint(self.update_stock) \ - and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): - from erpnext.accounts.general_ledger import delete_gl_entries - delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock: + delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) def make_supplier_gl_entry(self, gl_entries): - # parent's gl entry if self.grand_total: # Didnot use base_grand_total to book rounding loss gle grand_total_in_company_currency = flt(self.grand_total * self.conversion_rate, @@ -406,21 +401,62 @@ class PurchaseInvoice(BuyingController): def make_item_gl_entries(self, gl_entries): # item gl entries stock_items = self.get_stock_items() + expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + warehouse_account = get_warehouse_account() for item in self.get("items"): if flt(item.base_net_amount): account_currency = get_account_currency(item.expense_account) - gl_entries.append( - self.get_gl_dict({ - "account": item.expense_account, - "against": self.supplier, - "debit": item.base_net_amount, - "debit_in_account_currency": item.base_net_amount \ - if account_currency==self.company_currency else item.net_amount, - "cost_center": item.cost_center - }, account_currency) - ) + if self.update_stock and self.auto_accounting_for_stock: + val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9 + + # warehouse account + warehouse_debit_amount = flt(flt(item.valuation_rate, val_rate_db_precision) + * flt(item.qty) * flt(item.conversion_factor), item.precision("base_net_amount")) + + gl_entries.append( + self.get_gl_dict({ + "account": item.expense_account, + "against": self.supplier, + "debit": warehouse_debit_amount, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "cost_center": item.cost_center + }, account_currency) + ) + + # Amount added through landed-cost-voucher + if flt(item.landed_cost_voucher_amount): + gl_entries.append(self.get_gl_dict({ + "account": expenses_included_in_valuation, + "against": item.expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount) + })) + + # sub-contracting warehouse + if flt(item.rm_supp_cost): + supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["name"] + gl_entries.append(self.get_gl_dict({ + "account": supplier_warehouse_account, + "against": item.expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.rm_supp_cost) + }, warehouse_account[self.supplier_warehouse]["account_currency"])) + else: + gl_entries.append( + self.get_gl_dict({ + "account": item.expense_account, + "against": self.supplier, + "debit": flt(item.base_net_amount, item.precision("base_net_amount")), + "debit_in_account_currency": (flt(item.base_net_amount, + item.precision("base_net_amount")) if account_currency==self.company_currency + else flt(item.net_amount, item.precision("net_amount"))), + "cost_center": item.cost_center + }, account_currency) + ) if self.auto_accounting_for_stock and self.is_opening == "No" and \ item.item_code in stock_items and item.item_tax_amount: @@ -435,13 +471,13 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict({ "account": self.stock_received_but_not_billed, "against": self.supplier, - "debit": flt(item.item_tax_amount, self.precision("item_tax_amount", item)), + "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or "Accounting Entry for Stock" }) ) self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ - self.precision("item_tax_amount", item)) + item.precision("item_tax_amount")) def make_tax_gl_entries(self, gl_entries): # tax table gl entries @@ -558,7 +594,9 @@ class PurchaseInvoice(BuyingController): def on_cancel(self): self.check_for_closed_status() - + + self.update_status_updater_args() + if not self.is_return: from erpnext.accounts.utils import remove_against_link_from_jv remove_against_link_from_jv(self.doctype, self.name) @@ -567,6 +605,11 @@ class PurchaseInvoice(BuyingController): self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_in_pr() + # Updating stock ledger should always be called after updating prevdoc status, + # because updating ordered qty in bin depends upon updated ordered qty in PO + if self.update_stock == 1: + self.update_stock_ledger() + self.make_gl_entries_on_cancel() self.update_project() self.validate_asset() diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c0691744a6..9afcd21b90 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import unittest import frappe import frappe.model -from frappe.utils import cint +from frappe.utils import cint, flt import frappe.defaults from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ test_records as pr_test_records @@ -120,20 +120,20 @@ class TestPurchaseInvoice(unittest.TestCase): set_perpetual_inventory(0) def test_purchase_invoice_calculation(self): - wrapper = frappe.copy_doc(test_records[0]) - wrapper.insert() - wrapper.load_from_db() + pi = frappe.copy_doc(test_records[0]) + pi.insert() + pi.load_from_db() expected_values = [ ["_Test Item Home Desktop 100", 90, 59], ["_Test Item Home Desktop 200", 135, 177] ] - for i, item in enumerate(wrapper.get("items")): + for i, item in enumerate(pi.get("items")): self.assertEqual(item.item_code, expected_values[i][0]) self.assertEqual(item.item_tax_amount, expected_values[i][1]) self.assertEqual(item.valuation_rate, expected_values[i][2]) - self.assertEqual(wrapper.base_net_total, 1250) + self.assertEqual(pi.base_net_total, 1250) # tax amounts expected_values = [ @@ -147,7 +147,7 @@ class TestPurchaseInvoice(unittest.TestCase): ["_Test Account Discount - _TC", 168.03, 1512.30], ] - for i, tax in enumerate(wrapper.get("taxes")): + for i, tax in enumerate(pi.get("taxes")): self.assertEqual(tax.account_head, expected_values[i][0]) self.assertEqual(tax.tax_amount, expected_values[i][1]) self.assertEqual(tax.total, expected_values[i][2]) @@ -375,8 +375,28 @@ class TestPurchaseInvoice(unittest.TestCase): pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1) actual_qty_2 = get_qty_after_transaction() - self.assertEquals(actual_qty_1 - 2, actual_qty_2) + + pi1.cancel() + self.assertEquals(actual_qty_1, get_qty_after_transaction()) + + pi.cancel() + self.assertEquals(actual_qty_0, get_qty_after_transaction()) + + def test_subcontracting_via_purchase_invoice(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100) + make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC", + qty=100, basic_rate=100) + + pi = make_purchase_invoice(item_code="_Test FG Item", qty=10, rate=500, + update_stock=1, is_subcontracted="Yes") + + self.assertEquals(len(pi.get("supplied_items")), 2) + + rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")]) + self.assertEquals(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) def make_purchase_invoice(**args): pi = frappe.new_doc("Purchase Invoice") @@ -389,6 +409,7 @@ def make_purchase_invoice(**args): pi.update_stock = 1 if args.is_paid: pi.is_paid = 1 + if args.cash_bank_account: pi.cash_bank_account=args.cash_bank_account @@ -398,6 +419,8 @@ def make_purchase_invoice(**args): pi.conversion_rate = args.conversion_rate or 1 pi.is_return = args.is_return pi.return_against = args.return_against + pi.is_subcontracted = args.is_subcontracted + pi.supplier_warehouse = "_Test Warehouse 1 - _TC" pi.append("items", { "item_code": args.item or args.item_code or "_Test Item", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json index 4218828470..7feca2380f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_records.json +++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json @@ -22,7 +22,8 @@ "parentfield": "items", "qty": 10, "rate": 50, - "uom": "_Test UOM" + "uom": "_Test UOM", + "warehouse": "_Test Warehouse - _TC" }, { "amount": 750, @@ -37,7 +38,8 @@ "parentfield": "items", "qty": 5, "rate": 150, - "uom": "_Test UOM" + "uom": "_Test UOM", + "warehouse": "_Test Warehouse - _TC" } ], "grand_total": 0, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 265c38b91a..53747bfc82 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -81,14 +81,10 @@ class SalesInvoice(SellingController): self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") self.update_packing_list() - def on_submit(self): - if cint(self.update_stock) == 1: - self.update_stock_ledger() - else: - # Check for Approving Authority - if not self.recurring_id: - frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, - self.company, self.base_grand_total, self) + def on_submit(self): + if not self.recurring_id: + frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, + self.company, self.base_grand_total, self) self.check_prev_docstatus() @@ -99,7 +95,12 @@ class SalesInvoice(SellingController): self.update_status_updater_args() self.update_prevdoc_status() self.update_billing_status_in_dn() - + + # Updating stock ledger should always be called after updating prevdoc status, + # because updating reserved qty in bin depends upon updated delivered qty in SO + if self.update_stock == 1: + self.update_stock_ledger() + # this sequence because outstanding may get -ve self.make_gl_entries() @@ -117,9 +118,6 @@ class SalesInvoice(SellingController): self.update_time_log_batch(None) def on_cancel(self): - if cint(self.update_stock) == 1: - self.update_stock_ledger() - self.check_close_sales_order("sales_order") from erpnext.accounts.utils import remove_against_link_from_jv @@ -137,6 +135,11 @@ class SalesInvoice(SellingController): self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.validate_c_form_on_cancel() + + # Updating stock ledger should always be called after updating prevdoc status, + # because updating reserved qty in bin depends upon updated delivered qty in SO + if self.update_stock == 1: + self.update_stock_ledger() self.make_gl_entries_on_cancel() diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 94dd07067d..c24bcdcd92 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -45,6 +45,32 @@ class TestPurchaseOrder(unittest.TestCase): po.load_from_db() self.assertEquals(po.get("items")[0].received_qty, 4) + + def test_ordered_qty_against_pi_with_update_stock(self): + existing_ordered_qty = get_ordered_qty() + + po = create_purchase_order() + + self.assertEqual(get_ordered_qty(), existing_ordered_qty + 10) + + frappe.db.set_value('Item', '_Test Item', 'tolerance', 50) + + pi = make_purchase_invoice(po.name) + pi.update_stock = 1 + pi.items[0].qty = 12 + pi.insert() + pi.submit() + + self.assertEqual(get_ordered_qty(), existing_ordered_qty) + + po.load_from_db() + self.assertEquals(po.get("items")[0].received_qty, 12) + + pi.cancel() + self.assertEqual(get_ordered_qty(), existing_ordered_qty + 10) + + po.load_from_db() + self.assertEquals(po.get("items")[0].received_qty, 0) def test_make_purchase_invoice(self): po = create_purchase_order(do_not_submit=True) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7691ff5b5f..594a576a79 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -146,14 +146,6 @@ class AccountsController(TransactionBase): """set missing item values""" from erpnext.stock.get_item_details import get_item_details - if self.doctype == "Purchase Invoice": - auto_accounting_for_stock = cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) - - if auto_accounting_for_stock: - stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") - - stock_items = self.get_stock_items() - if hasattr(self, "items"): parent_dict = {} for fieldname in self.meta.get_valid_columns(): @@ -200,14 +192,8 @@ class AccountsController(TransactionBase): item.rate = flt(item.price_list_rate * (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate")) - if self.doctype == "Purchase Invoice": - if auto_accounting_for_stock and item.item_code in stock_items \ - and self.is_opening == 'No' \ - and (not item.po_detail or not frappe.db.get_value("Purchase Order Item", - item.po_detail, "delivered_by_supplier")): - - item.expense_account = stock_not_billed_account - item.cost_center = None + if self.doctype == "Purchase Invoice": + self.set_expense_account() def set_taxes(self): if not self.meta.get_field("taxes"): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 9c9f1c3ac8..74a3d696e0 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -33,10 +33,10 @@ class BuyingController(StockController): self.validate_stock_or_nonstock_items() self.validate_warehouse() - if self.doctype=="Purchase Invoice" and getattr(self, "update_stock"): - self.validate_purchase_receipt() + if self.doctype=="Purchase Invoice": + self.validate_purchase_receipt_if_update_stock() - if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and getattr(self, "update_stock")): + if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and self.update_stock): self.validate_purchase_return() self.validate_rejected_warehouse() self.validate_accepted_rejected_qty() @@ -48,11 +48,13 @@ class BuyingController(StockController): self.validate_for_subcontracting() self.create_raw_materials_supplied("supplied_items") self.set_landed_cost_voucher_amount() + + if self.doctype in ("Purchase Receipt", "Purchase Invoice"): self.update_valuation_rate("items") def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) - + self.set_supplier_from_item_default() self.set_price_list_currency("Buying") @@ -118,7 +120,6 @@ class BuyingController(StockController): if item.item_code and item.qty and item.item_code in stock_items: item_proportion = flt(item.base_net_amount) / stock_items_amount if stock_items_amount \ else flt(item.qty) / stock_items_qty - if i == (last_stock_item_idx - 1): item.item_tax_amount = flt(valuation_amount_adjustment, self.precision("item_tax_amount", item)) @@ -229,7 +230,7 @@ class BuyingController(StockController): rm.amount = required_qty * flt(rm.rate) raw_materials_cost += flt(rm.amount) - if self.doctype == "Purchase Receipt": + if self.doctype in ("Purchase Receipt", "Purchase Invoice"): item.rm_supp_cost = raw_materials_cost def cleanup_raw_materials_supplied(self, parent_items, raw_material_table): @@ -319,6 +320,8 @@ class BuyingController(StockController): frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): + self.update_ordered_qty() + sl_entries = [] stock_items = self.get_stock_items() @@ -354,6 +357,26 @@ class BuyingController(StockController): self.make_sl_entries_for_supplier_warehouse(sl_entries) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) + + def update_ordered_qty(self): + po_map = {} + for d in self.get("items"): + if self.doctype=="Purchase Receipt" \ + and d.prevdoc_doctype=="Purchase Order" and d.prevdoc_detail_docname: + po_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname) + + elif self.doctype=="Purchase Invoice" and d.purchase_order and d.po_detail: + po_map.setdefault(d.purchase_order, []).append(d.po_detail) + + for po, po_item_rows in po_map.items(): + if po and po_item_rows: + po_obj = frappe.get_doc("Purchase Order", po) + + if po_obj.status in ["Closed", "Cancelled"]: + frappe.throw(_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po), + frappe.InvalidStatusError) + + po_obj.update_ordered_qty(po_item_rows) def make_sl_entries_for_supplier_warehouse(self, sl_entries): if hasattr(self, 'supplied_items'): diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 3c9efa10d7..b9b94f512a 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -7,6 +7,7 @@ from frappe.utils import cint, flt, cstr, comma_or from erpnext.setup.utils import get_company_currency from frappe import _, throw from erpnext.stock.get_item_details import get_bin_details +from erpnext.stock.utils import get_incoming_rate from erpnext.controllers.stock_controller import StockController @@ -228,6 +229,79 @@ class SellingController(StockController): status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status") if status == "Closed": frappe.throw(_("Sales Order {0} is {1}").format(d.get(ref_fieldname), status)) + + def update_reserved_qty(self): + so_map = {} + for d in self.get("items"): + if d.so_detail: + if self.doctype == "Delivery Note" and d.against_sales_order: + so_map.setdefault(d.against_sales_order, []).append(d.so_detail) + elif self.doctype == "Sales Invoice" and d.sales_order and self.update_stock: + so_map.setdefault(d.sales_order, []).append(d.so_detail) + + for so, so_item_rows in so_map.items(): + if so and so_item_rows: + sales_order = frappe.get_doc("Sales Order", so) + + if sales_order.status in ["Closed", "Cancelled"]: + frappe.throw(_("{0} {1} is cancelled or closed").format(_("Sales Order"), so), + frappe.InvalidStatusError) + + sales_order.update_reserved_qty(so_item_rows) + + def update_stock_ledger(self): + self.update_reserved_qty() + + sl_entries = [] + for d in self.get_item_list(): + if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty): + return_rate = 0 + if cint(self.is_return) and self.return_against and self.docstatus==1: + return_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) + + # On cancellation or if return entry submission, make stock ledger entry for + # target warehouse first, to update serial no values properly + + if d.warehouse and ((not cint(self.is_return) and self.docstatus==1) + or (cint(self.is_return) and self.docstatus==2)): + sl_entries.append(self.get_sl_entries(d, { + "actual_qty": -1*flt(d.qty), + "incoming_rate": return_rate + })) + + if d.target_warehouse: + target_warehouse_sle = self.get_sl_entries(d, { + "actual_qty": flt(d.qty), + "warehouse": d.target_warehouse + }) + + if self.docstatus == 1: + if not cint(self.is_return): + args = frappe._dict({ + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1*flt(d.qty), + "serial_no": d.serial_no + }) + target_warehouse_sle.update({ + "incoming_rate": get_incoming_rate(args) + }) + else: + target_warehouse_sle.update({ + "outgoing_rate": return_rate + }) + sl_entries.append(target_warehouse_sle) + + if d.warehouse and ((not cint(self.is_return) and self.docstatus==2) + or (cint(self.is_return) and self.docstatus==1)): + sl_entries.append(self.get_sl_entries(d, { + "actual_qty": -1*flt(d.qty), + "incoming_rate": return_rate + })) + + self.make_sl_entries(sl_entries) def check_active_sales_items(obj): for d in obj.get("items"): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index bd1f258a8d..845a4d0744 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -8,8 +8,6 @@ from frappe import msgprint, _ import frappe.defaults from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map -from erpnext.stock.utils import get_incoming_rate - from erpnext.controllers.accounts_controller import AccountsController class StockController(AccountsController): @@ -230,79 +228,6 @@ class StockController(AccountsController): incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 return incoming_rate - - def update_reserved_qty(self): - so_map = {} - for d in self.get("items"): - if d.so_detail: - if self.doctype == "Delivery Note" and d.against_sales_order: - so_map.setdefault(d.against_sales_order, []).append(d.so_detail) - elif self.doctype == "Sales Invoice" and d.sales_order and self.update_stock: - so_map.setdefault(d.sales_order, []).append(d.so_detail) - - for so, so_item_rows in so_map.items(): - if so and so_item_rows: - sales_order = frappe.get_doc("Sales Order", so) - - if sales_order.status in ["Closed", "Cancelled"]: - frappe.throw(_("{0} {1} is cancelled or closed").format(_("Sales Order"), so), - frappe.InvalidStatusError) - - sales_order.update_reserved_qty(so_item_rows) - - def update_stock_ledger(self): - self.update_reserved_qty() - - sl_entries = [] - for d in self.get_item_list(): - if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty): - return_rate = 0 - if cint(self.is_return) and self.return_against and self.docstatus==1: - return_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) - - # On cancellation or if return entry submission, make stock ledger entry for - # target warehouse first, to update serial no values properly - - if d.warehouse and ((not cint(self.is_return) and self.docstatus==1) - or (cint(self.is_return) and self.docstatus==2)): - sl_entries.append(self.get_sl_entries(d, { - "actual_qty": -1*flt(d.qty), - "incoming_rate": return_rate - })) - - if d.target_warehouse: - target_warehouse_sle = self.get_sl_entries(d, { - "actual_qty": flt(d.qty), - "warehouse": d.target_warehouse - }) - - if self.docstatus == 1: - if not cint(self.is_return): - args = frappe._dict({ - "item_code": d.item_code, - "warehouse": d.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1*flt(d.qty), - "serial_no": d.serial_no - }) - target_warehouse_sle.update({ - "incoming_rate": get_incoming_rate(args) - }) - else: - target_warehouse_sle.update({ - "outgoing_rate": return_rate - }) - sl_entries.append(target_warehouse_sle) - - if d.warehouse and ((not cint(self.is_return) and self.docstatus==2) - or (cint(self.is_return) and self.docstatus==1)): - sl_entries.append(self.get_sl_entries(d, { - "actual_qty": -1*flt(d.qty), - "incoming_rate": return_rate - })) - - self.make_sl_entries(sl_entries) def validate_warehouse(self): from erpnext.stock.utils import validate_warehouse_company diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 469e327245..ccc4629448 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -196,7 +196,7 @@ execute:frappe.db.sql("update `tabProduction Order` pro set description = (selec 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.v4_2.repost_reserved_qty #2016-04-15 erpnext.patches.v5_4.update_purchase_cost_against_project erpnext.patches.v5_8.update_order_reference_in_return_entries erpnext.patches.v5_8.add_credit_note_print_heading diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 7ac8941808..5fa2d7e2ba 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -126,6 +126,34 @@ class TestSalesOrder(unittest.TestCase): dn.cancel() self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10) + + def test_reserved_qty_for_over_delivery_via_sales_invoice(self): + # set over-delivery tolerance + frappe.db.set_value('Item', "_Test Item", 'tolerance', 50) + + existing_reserved_qty = get_reserved_qty() + + so = make_sales_order() + self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10) + + si = make_sales_invoice(so.name) + si.update_stock = 1 + si.get("items")[0].qty = 12 + si.insert() + si.submit() + + self.assertEqual(get_reserved_qty(), existing_reserved_qty) + + so.load_from_db() + self.assertEqual(so.get("items")[0].delivered_qty, 12) + self.assertEqual(so.per_delivered, 100) + + si.cancel() + self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10) + + so.load_from_db() + self.assertEqual(so.get("items")[0].delivered_qty, 0) + self.assertEqual(so.per_delivered, 0) def test_reserved_qty_for_partial_delivery_with_packing_list(self): existing_reserved_qty_item1 = get_reserved_qty("_Test Item") diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 63abaa5a34..6712ee85af 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -191,6 +191,8 @@ class DeliveryNote(SellingController): if not self.is_return: self.check_credit_limit() + # Updating stock ledger should always be called after updating prevdoc status, + # because updating reserved qty in bin depends upon updated delivered qty in SO self.update_stock_ledger() self.make_gl_entries() @@ -201,6 +203,8 @@ class DeliveryNote(SellingController): self.update_prevdoc_status() self.update_billing_status() + # Updating stock ledger should always be called after updating prevdoc status, + # because updating reserved qty in bin depends upon updated delivered qty in SO self.update_stock_ledger() self.cancel_packing_slips() diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 81708ce83d..c58607fe05 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -14,9 +14,6 @@ class TestLandedCostVoucher(unittest.TestCase): set_perpetual_inventory(1) pr = frappe.copy_doc(pr_test_records[0]) pr.submit() - - pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), - posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1) last_sle = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pr.doctype, @@ -24,16 +21,12 @@ class TestLandedCostVoucher(unittest.TestCase): "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC" }, - fieldname=["qty_after_transaction", "stock_value"], - as_dict=1) + fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - self.submit_landed_cost_voucher(pr, pi) + self.submit_landed_cost_voucher("Purchase Receipt", pr.name) pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount") self.assertEquals(pr_lc_value, 25.0) - - pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, "landed_cost_voucher_amount") - self.assertEquals(pi_lc_value, 25.0) last_sle_after_landed_cost = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pr.doctype, @@ -41,8 +34,7 @@ class TestLandedCostVoucher(unittest.TestCase): "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC" }, - fieldname=["qty_after_transaction", "stock_value"], - as_dict=1) + fieldname=["qty_after_transaction", "stock_value"], as_dict=1) self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction) @@ -68,7 +60,56 @@ class TestLandedCostVoucher(unittest.TestCase): self.assertEquals(expected_values[gle.account][1], gle.credit) set_perpetual_inventory(0) + + def test_landed_cost_voucher_against_purchase_invoice(self): + set_perpetual_inventory(1) + + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), + posting_time=frappe.utils.nowtime()) + last_sle = frappe.db.get_value("Stock Ledger Entry", { + "voucher_type": pi.doctype, + "voucher_no": pi.name, + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC" + }, + fieldname=["qty_after_transaction", "stock_value"], as_dict=1) + + self.submit_landed_cost_voucher("Purchase Invoice", pi.name) + + pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, + "landed_cost_voucher_amount") + + self.assertEquals(pi_lc_value, 50.0) + + last_sle_after_landed_cost = frappe.db.get_value("Stock Ledger Entry", { + "voucher_type": pi.doctype, + "voucher_no": pi.name, + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC" + }, + fieldname=["qty_after_transaction", "stock_value"], as_dict=1) + + self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction) + + self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0) + + gl_entries = get_gl_entries("Purchase Invoice", pi.name) + + self.assertTrue(gl_entries) + + expected_values = { + pi.get("items")[0].warehouse: [300.0, 0.0], + "Creditors - _TC": [0.0, 250.0], + "Expenses Included In Valuation - _TC": [0.0, 50.0] + } + + for gle in gl_entries: + self.assertEquals(expected_values[gle.account][0], gle.debit) + self.assertEquals(expected_values[gle.account][1], gle.credit) + + set_perpetual_inventory(0) + def test_landed_cost_voucher_for_serialized_item(self): set_perpetual_inventory(1) frappe.db.sql("delete from `tabSerial No` where name in ('SN001', 'SN002', 'SN003', 'SN004', 'SN005')") @@ -80,40 +121,33 @@ class TestLandedCostVoucher(unittest.TestCase): serial_no_rate = frappe.db.get_value("Serial No", "SN001", "purchase_rate") - self.submit_landed_cost_voucher(pr) + self.submit_landed_cost_voucher("Purchase Receipt", pr.name) serial_no = frappe.db.get_value("Serial No", "SN001", ["warehouse", "purchase_rate"], as_dict=1) - self.assertEquals(serial_no.purchase_rate - serial_no_rate, 7.5) + self.assertEquals(serial_no.purchase_rate - serial_no_rate, 5.0) self.assertEquals(serial_no.warehouse, "_Test Warehouse - _TC") set_perpetual_inventory(0) - def submit_landed_cost_voucher(self, pr, pi=None): + def submit_landed_cost_voucher(self, receipt_document_type, receipt_document): + ref_doc = frappe.get_doc(receipt_document_type, receipt_document) + lcv = frappe.new_doc("Landed Cost Voucher") lcv.company = "_Test Company" lcv.set("purchase_receipts", [{ - "receipt_document_type": "Purchase Receipt", - "receipt_document": pr.name, - "supplier": pr.supplier, - "posting_date": pr.posting_date, - "grand_total": pr.base_grand_total + "receipt_document_type": receipt_document_type, + "receipt_document": receipt_document, + "supplier": ref_doc.supplier, + "posting_date": ref_doc.posting_date, + "grand_total": ref_doc.base_grand_total }]) - if pi: - lcv.append("purchase_receipts", { - "receipt_document_type": "Purchase Invoice", - "receipt_document": pi.name, - "supplier": pi.supplier, - "posting_date": pi.posting_date, - "grand_total": pi.base_grand_total - }) - lcv.set("taxes", [{ "description": "Insurance Charges", "account": "_Test Account Insurance Charges - _TC", - "amount": 75.0 + "amount": 50 }]) lcv.insert() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 332dc63c68..9b9581b073 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -21,6 +21,20 @@ frappe.ui.form.on("Purchase Receipt", { "batch_no": doc.batch_no } } + + $.each(["warehouse", "rejected_warehouse"], function(i, field) { + frm.set_query(field, "items", function() { + return { + filters: [["Warehouse", "company", "in", ["", cstr(frm.doc.company)]]] + } + }) + }) + + frm.set_query("supplier_warehouse", function() { + return { + filters: [["Warehouse", "company", "in", ["", cstr(frm.doc.company)]]] + } + }) } }); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index bad1228dd2..1babf2ea43 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cstr, flt, cint +from frappe.utils import flt, cint from frappe import _ import frappe.defaults @@ -81,22 +81,6 @@ class PurchaseReceipt(BuyingController): if not d.prevdoc_docname: frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) - def update_ordered_qty(self): - po_map = {} - for d in self.get("items"): - if d.prevdoc_doctype and d.prevdoc_doctype == "Purchase Order" and d.prevdoc_detail_docname: - po_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname) - - for po, po_item_rows in po_map.items(): - if po and po_item_rows: - po_obj = frappe.get_doc("Purchase Order", po) - - if po_obj.status in ["Closed", "Cancelled"]: - frappe.throw(_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po), - frappe.InvalidStatusError) - - po_obj.update_ordered_qty(po_item_rows) - def get_already_received_qty(self, po, po_detail): qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item` where prevdoc_detail_docname = %s and docstatus = 1 @@ -129,19 +113,20 @@ class PurchaseReceipt(BuyingController): purchase_controller = frappe.get_doc("Purchase Common") # Check for Approving Authority - frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) + frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, + self.company, self.base_grand_total) # Set status as Submitted frappe.db.set(self, 'status', 'Submitted') self.update_prevdoc_status() - self.update_ordered_qty() - self.update_billing_status() if not self.is_return: purchase_controller.update_last_purchase_rate(self, 1) + # Updating stock ledger should always be called after updating prevdoc status, + # because updating ordered qty in bin depends upon updated ordered qty in PO self.update_stock_ledger() from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit @@ -171,17 +156,15 @@ class PurchaseReceipt(BuyingController): frappe.db.set(self,'status','Cancelled') - self.update_stock_ledger() - - self.update_prevdoc_status() - # Must be called after updating received qty in PO - self.update_ordered_qty() - + self.update_prevdoc_status() self.update_billing_status() if not self.is_return: pc_obj.update_last_purchase_rate(self, 0) - + + # Updating stock ledger should always be called after updating prevdoc status, + # because updating ordered qty in bin depends upon updated ordered qty in PO + self.update_stock_ledger() self.make_gl_entries_on_cancel() def get_current_stock(self): @@ -256,13 +239,11 @@ class PurchaseReceipt(BuyingController): }, warehouse_account[self.supplier_warehouse]["account_currency"])) # divisional loss adjustment - sle_valuation_amount = flt(flt(d.valuation_rate, val_rate_db_precision) * flt(d.qty) * flt(d.conversion_factor), - self.precision("base_net_amount", d)) - - distributed_amount = flt(flt(d.base_net_amount, self.precision("base_net_amount", d))) + \ + distributed_amount = flt(flt(d.base_net_amount, d.precision("base_net_amount"))) + \ flt(d.landed_cost_voucher_amount) + flt(d.rm_supp_cost) + flt(d.item_tax_amount) - divisional_loss = flt(distributed_amount - sle_valuation_amount, self.precision("base_net_amount", d)) + divisional_loss = flt(distributed_amount - stock_value_diff, + d.precision("base_net_amount")) if divisional_loss: gl_entries.append(self.get_gl_dict({ "account": stock_rbnb,