[fix] Cleanup and fixes in update stock feature in Purchase Invoice

This commit is contained in:
Nabin Hait 2016-04-18 15:54:01 +05:30
parent ffd1e4efe6
commit 14aa9c5320
16 changed files with 429 additions and 235 deletions

View File

@ -15,30 +15,30 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
this.frm.set_df_property("credit_to", "print_hide", 0); this.frm.set_df_property("credit_to", "print_hide", 0);
} }
} }
hide_fields(this.frm.doc);
}, },
refresh: function(doc) { refresh: function(doc) {
this._super(); this._super();
hide_fields(this.frm.doc);
// Show / Hide button // Show / Hide button
this.show_general_ledger(); 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.is_return && doc.docstatus==1) {
if(doc.outstanding_amount > 0) { if(doc.outstanding_amount > 0) {
this.frm.add_custom_button(__('Payment'), this.make_bank_entry, __("Make")); this.frm.add_custom_button(__('Payment'), this.make_bank_entry, __("Make"));
cur_frm.page.set_inner_btn_group_as_primary(__("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)) { 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'), cur_frm.add_custom_button(doc.update_stock ? __('Purchase Return') : __('Debit Note'),
this.make_debit_note, __("Make")); this.make_debit_note, __("Make"));
} }
if(doc.update_stock==1) {
this.show_stock_ledger();
}
} }
if(doc.docstatus===0) { if(doc.docstatus===0) {
@ -69,6 +69,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
}) })
}, __("Get items from")); }, __("Get items from"));
} }
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
}, },
supplier: function() { supplier: function() {
@ -326,3 +328,29 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
else else
cur_frm.pformat.print_heading = __("Purchase Invoice"); 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");
}
})

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint, formatdate, flt, getdate 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 from erpnext.setup.utils import get_company_currency
import frappe.defaults 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.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.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po
from erpnext.controllers.stock_controller import get_warehouse_account 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 = { form_grid_templates = {
"items": "templates/form_grid/item_grid.html" "items": "templates/form_grid/item_grid.html"
@ -51,7 +52,6 @@ class PurchaseInvoice(BuyingController):
if (self.is_paid == 1): if (self.is_paid == 1):
self.validate_cash() self.validate_cash()
self.check_active_purchase_items()
self.check_conversion_rate() self.check_conversion_rate()
self.validate_credit_to_acc() self.validate_credit_to_acc()
self.clear_unallocated_advances("Purchase Invoice Advance", "advances") self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
@ -61,7 +61,6 @@ class PurchaseInvoice(BuyingController):
self.set_expense_account() self.set_expense_account()
self.set_against_expense_account() self.set_against_expense_account()
self.validate_write_off_account() self.validate_write_off_account()
self.update_valuation_rate("items")
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items") self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
self.validate_fixed_asset_account() self.validate_fixed_asset_account()
self.create_remarks() self.create_remarks()
@ -177,7 +176,6 @@ class PurchaseInvoice(BuyingController):
else: else:
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
item.cost_center = None
elif not item.expense_account: elif not item.expense_account:
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) 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): def validate_purchase_receipt_if_update_stock(self):
for item in self.get("items"): if self.update_stock:
if item.purchase_receipt: for item in self.get("items"):
frappe.throw(_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)) if item.purchase_receipt:
frappe.throw(_("Stock cannot be updated against Purchase Receipt {0}")
.format(item.purchase_receipt))
def on_submit(self): def on_submit(self):
self.check_prev_docstatus() self.check_prev_docstatus()
self.update_status_updater_args()
self.validate_asset() self.validate_asset()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total) 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: if not self.is_return:
self.update_against_document_in_jv() self.update_against_document_in_jv()
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr() 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() self.update_project()
def validate_asset(self): def validate_asset(self):
@ -353,38 +354,32 @@ class PurchaseInvoice(BuyingController):
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
self.make_tax_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) gl_entries = merge_similar_entries(gl_entries)
self.make_payment_gl_entries(gl_entries) self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries) self.make_write_off_gl_entry(gl_entries)
if 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" update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
update_outstanding=update_outstanding, merge_entries=False) update_outstanding=update_outstanding, merge_entries=False)
if update_outstanding == "No": 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, update_outstanding_amt(self.credit_to, "Supplier", self.supplier,
self.doctype, self.return_against if cint(self.is_return) else self.name) self.doctype, self.return_against if cint(self.is_return) else self.name)
if repost_future_gle and cint(self.update_stock) \ if repost_future_gle and cint(self.update_stock) and self.auto_accounting_for_stock:
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): from erpnext.controllers.stock_controller import update_gl_entries_after
from erpnext.controllers.stock_controller import update_gl_entries_after items, warehouses = self.get_items_and_warehouses()
items, warehouses = self.get_items_and_warehouses() update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items)
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items)
elif self.docstatus == 2 and cint(self.update_stock) \ elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
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): def make_supplier_gl_entry(self, gl_entries):
# parent's gl entry
if self.grand_total: if self.grand_total:
# Didnot use base_grand_total to book rounding loss gle # Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(self.grand_total * self.conversion_rate, 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): def make_item_gl_entries(self, gl_entries):
# item gl entries # item gl entries
stock_items = self.get_stock_items() 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"): for item in self.get("items"):
if flt(item.base_net_amount): if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account) account_currency = get_account_currency(item.expense_account)
gl_entries.append( if self.update_stock and self.auto_accounting_for_stock:
self.get_gl_dict({ val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
"account": item.expense_account,
"against": self.supplier, # warehouse account
"debit": item.base_net_amount, warehouse_debit_amount = flt(flt(item.valuation_rate, val_rate_db_precision)
"debit_in_account_currency": item.base_net_amount \ * flt(item.qty) * flt(item.conversion_factor), item.precision("base_net_amount"))
if account_currency==self.company_currency else item.net_amount,
"cost_center": item.cost_center gl_entries.append(
}, account_currency) 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 \ if self.auto_accounting_for_stock and self.is_opening == "No" and \
item.item_code in stock_items and item.item_tax_amount: item.item_code in stock_items and item.item_tax_amount:
@ -435,13 +471,13 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict({ self.get_gl_dict({
"account": self.stock_received_but_not_billed, "account": self.stock_received_but_not_billed,
"against": self.supplier, "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" "remarks": self.remarks or "Accounting Entry for Stock"
}) })
) )
self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ 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): def make_tax_gl_entries(self, gl_entries):
# tax table gl entries # tax table gl entries
@ -558,7 +594,9 @@ class PurchaseInvoice(BuyingController):
def on_cancel(self): def on_cancel(self):
self.check_for_closed_status() self.check_for_closed_status()
self.update_status_updater_args()
if not self.is_return: if not self.is_return:
from erpnext.accounts.utils import remove_against_link_from_jv from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name) 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_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr() 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.make_gl_entries_on_cancel()
self.update_project() self.update_project()
self.validate_asset() self.validate_asset()

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
import frappe.model import frappe.model
from frappe.utils import cint from frappe.utils import cint, flt
import frappe.defaults import frappe.defaults
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
test_records as pr_test_records test_records as pr_test_records
@ -120,20 +120,20 @@ class TestPurchaseInvoice(unittest.TestCase):
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_purchase_invoice_calculation(self): def test_purchase_invoice_calculation(self):
wrapper = frappe.copy_doc(test_records[0]) pi = frappe.copy_doc(test_records[0])
wrapper.insert() pi.insert()
wrapper.load_from_db() pi.load_from_db()
expected_values = [ expected_values = [
["_Test Item Home Desktop 100", 90, 59], ["_Test Item Home Desktop 100", 90, 59],
["_Test Item Home Desktop 200", 135, 177] ["_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_code, expected_values[i][0])
self.assertEqual(item.item_tax_amount, expected_values[i][1]) self.assertEqual(item.item_tax_amount, expected_values[i][1])
self.assertEqual(item.valuation_rate, expected_values[i][2]) 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 # tax amounts
expected_values = [ expected_values = [
@ -147,7 +147,7 @@ class TestPurchaseInvoice(unittest.TestCase):
["_Test Account Discount - _TC", 168.03, 1512.30], ["_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.account_head, expected_values[i][0])
self.assertEqual(tax.tax_amount, expected_values[i][1]) self.assertEqual(tax.tax_amount, expected_values[i][1])
self.assertEqual(tax.total, expected_values[i][2]) 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) 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() actual_qty_2 = get_qty_after_transaction()
self.assertEquals(actual_qty_1 - 2, actual_qty_2) 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): def make_purchase_invoice(**args):
pi = frappe.new_doc("Purchase Invoice") pi = frappe.new_doc("Purchase Invoice")
@ -389,6 +409,7 @@ def make_purchase_invoice(**args):
pi.update_stock = 1 pi.update_stock = 1
if args.is_paid: if args.is_paid:
pi.is_paid = 1 pi.is_paid = 1
if args.cash_bank_account: if args.cash_bank_account:
pi.cash_bank_account=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.conversion_rate = args.conversion_rate or 1
pi.is_return = args.is_return pi.is_return = args.is_return
pi.return_against = args.return_against pi.return_against = args.return_against
pi.is_subcontracted = args.is_subcontracted
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.append("items", { pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item or args.item_code or "_Test Item",

View File

@ -22,7 +22,8 @@
"parentfield": "items", "parentfield": "items",
"qty": 10, "qty": 10,
"rate": 50, "rate": 50,
"uom": "_Test UOM" "uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC"
}, },
{ {
"amount": 750, "amount": 750,
@ -37,7 +38,8 @@
"parentfield": "items", "parentfield": "items",
"qty": 5, "qty": 5,
"rate": 150, "rate": 150,
"uom": "_Test UOM" "uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC"
} }
], ],
"grand_total": 0, "grand_total": 0,

View File

@ -81,14 +81,10 @@ class SalesInvoice(SellingController):
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
self.update_packing_list() self.update_packing_list()
def on_submit(self): def on_submit(self):
if cint(self.update_stock) == 1: if not self.recurring_id:
self.update_stock_ledger() frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
else: self.company, self.base_grand_total, self)
# 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)
self.check_prev_docstatus() self.check_prev_docstatus()
@ -99,7 +95,12 @@ class SalesInvoice(SellingController):
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_in_dn() 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 # this sequence because outstanding may get -ve
self.make_gl_entries() self.make_gl_entries()
@ -117,9 +118,6 @@ class SalesInvoice(SellingController):
self.update_time_log_batch(None) self.update_time_log_batch(None)
def on_cancel(self): def on_cancel(self):
if cint(self.update_stock) == 1:
self.update_stock_ledger()
self.check_close_sales_order("sales_order") self.check_close_sales_order("sales_order")
from erpnext.accounts.utils import remove_against_link_from_jv 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.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.validate_c_form_on_cancel() 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() self.make_gl_entries_on_cancel()

View File

@ -45,6 +45,32 @@ class TestPurchaseOrder(unittest.TestCase):
po.load_from_db() po.load_from_db()
self.assertEquals(po.get("items")[0].received_qty, 4) 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): def test_make_purchase_invoice(self):
po = create_purchase_order(do_not_submit=True) po = create_purchase_order(do_not_submit=True)

View File

@ -146,14 +146,6 @@ class AccountsController(TransactionBase):
"""set missing item values""" """set missing item values"""
from erpnext.stock.get_item_details import get_item_details 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"): if hasattr(self, "items"):
parent_dict = {} parent_dict = {}
for fieldname in self.meta.get_valid_columns(): for fieldname in self.meta.get_valid_columns():
@ -200,14 +192,8 @@ class AccountsController(TransactionBase):
item.rate = flt(item.price_list_rate * item.rate = flt(item.price_list_rate *
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate")) (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
if self.doctype == "Purchase Invoice": if self.doctype == "Purchase Invoice":
if auto_accounting_for_stock and item.item_code in stock_items \ self.set_expense_account()
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
def set_taxes(self): def set_taxes(self):
if not self.meta.get_field("taxes"): if not self.meta.get_field("taxes"):

View File

@ -33,10 +33,10 @@ class BuyingController(StockController):
self.validate_stock_or_nonstock_items() self.validate_stock_or_nonstock_items()
self.validate_warehouse() self.validate_warehouse()
if self.doctype=="Purchase Invoice" and getattr(self, "update_stock"): if self.doctype=="Purchase Invoice":
self.validate_purchase_receipt() 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_purchase_return()
self.validate_rejected_warehouse() self.validate_rejected_warehouse()
self.validate_accepted_rejected_qty() self.validate_accepted_rejected_qty()
@ -48,11 +48,13 @@ class BuyingController(StockController):
self.validate_for_subcontracting() self.validate_for_subcontracting()
self.create_raw_materials_supplied("supplied_items") self.create_raw_materials_supplied("supplied_items")
self.set_landed_cost_voucher_amount() self.set_landed_cost_voucher_amount()
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
self.update_valuation_rate("items") self.update_valuation_rate("items")
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate) super(BuyingController, self).set_missing_values(for_validate)
self.set_supplier_from_item_default() self.set_supplier_from_item_default()
self.set_price_list_currency("Buying") 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: 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 \ item_proportion = flt(item.base_net_amount) / stock_items_amount if stock_items_amount \
else flt(item.qty) / stock_items_qty else flt(item.qty) / stock_items_qty
if i == (last_stock_item_idx - 1): if i == (last_stock_item_idx - 1):
item.item_tax_amount = flt(valuation_amount_adjustment, item.item_tax_amount = flt(valuation_amount_adjustment,
self.precision("item_tax_amount", item)) self.precision("item_tax_amount", item))
@ -229,7 +230,7 @@ class BuyingController(StockController):
rm.amount = required_qty * flt(rm.rate) rm.amount = required_qty * flt(rm.rate)
raw_materials_cost += flt(rm.amount) 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 item.rm_supp_cost = raw_materials_cost
def cleanup_raw_materials_supplied(self, parent_items, raw_material_table): 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)) 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): def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
self.update_ordered_qty()
sl_entries = [] sl_entries = []
stock_items = self.get_stock_items() 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_for_supplier_warehouse(sl_entries)
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher) 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): def make_sl_entries_for_supplier_warehouse(self, sl_entries):
if hasattr(self, 'supplied_items'): if hasattr(self, 'supplied_items'):

View File

@ -7,6 +7,7 @@ from frappe.utils import cint, flt, cstr, comma_or
from erpnext.setup.utils import get_company_currency from erpnext.setup.utils import get_company_currency
from frappe import _, throw from frappe import _, throw
from erpnext.stock.get_item_details import get_bin_details 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 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") status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status")
if status == "Closed": if status == "Closed":
frappe.throw(_("Sales Order {0} is {1}").format(d.get(ref_fieldname), status)) 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): def check_active_sales_items(obj):
for d in obj.get("items"): for d in obj.get("items"):

View File

@ -8,8 +8,6 @@ from frappe import msgprint, _
import frappe.defaults import frappe.defaults
from erpnext.accounts.utils import get_fiscal_year 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.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 from erpnext.controllers.accounts_controller import AccountsController
class StockController(AccountsController): class StockController(AccountsController):
@ -230,79 +228,6 @@ class StockController(AccountsController):
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
return incoming_rate 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): def validate_warehouse(self):
from erpnext.stock.utils import validate_warehouse_company from erpnext.stock.utils import validate_warehouse_company

View File

@ -196,7 +196,7 @@ execute:frappe.db.sql("update `tabProduction Order` pro set description = (selec
erpnext.patches.v5_7.item_template_attributes 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")
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item") 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_4.update_purchase_cost_against_project
erpnext.patches.v5_8.update_order_reference_in_return_entries erpnext.patches.v5_8.update_order_reference_in_return_entries
erpnext.patches.v5_8.add_credit_note_print_heading erpnext.patches.v5_8.add_credit_note_print_heading

View File

@ -126,6 +126,34 @@ class TestSalesOrder(unittest.TestCase):
dn.cancel() dn.cancel()
self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10) 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): def test_reserved_qty_for_partial_delivery_with_packing_list(self):
existing_reserved_qty_item1 = get_reserved_qty("_Test Item") existing_reserved_qty_item1 = get_reserved_qty("_Test Item")

View File

@ -191,6 +191,8 @@ class DeliveryNote(SellingController):
if not self.is_return: if not self.is_return:
self.check_credit_limit() 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.update_stock_ledger()
self.make_gl_entries() self.make_gl_entries()
@ -201,6 +203,8 @@ class DeliveryNote(SellingController):
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_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.update_stock_ledger()
self.cancel_packing_slips() self.cancel_packing_slips()

View File

@ -14,9 +14,6 @@ class TestLandedCostVoucher(unittest.TestCase):
set_perpetual_inventory(1) set_perpetual_inventory(1)
pr = frappe.copy_doc(pr_test_records[0]) pr = frappe.copy_doc(pr_test_records[0])
pr.submit() 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", { last_sle = frappe.db.get_value("Stock Ledger Entry", {
"voucher_type": pr.doctype, "voucher_type": pr.doctype,
@ -24,16 +21,12 @@ class TestLandedCostVoucher(unittest.TestCase):
"item_code": "_Test Item", "item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC" "warehouse": "_Test Warehouse - _TC"
}, },
fieldname=["qty_after_transaction", "stock_value"], fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
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") pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount")
self.assertEquals(pr_lc_value, 25.0) 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", { last_sle_after_landed_cost = frappe.db.get_value("Stock Ledger Entry", {
"voucher_type": pr.doctype, "voucher_type": pr.doctype,
@ -41,8 +34,7 @@ class TestLandedCostVoucher(unittest.TestCase):
"item_code": "_Test Item", "item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC" "warehouse": "_Test Warehouse - _TC"
}, },
fieldname=["qty_after_transaction", "stock_value"], fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
as_dict=1)
self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction) 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) self.assertEquals(expected_values[gle.account][1], gle.credit)
set_perpetual_inventory(0) 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): def test_landed_cost_voucher_for_serialized_item(self):
set_perpetual_inventory(1) set_perpetual_inventory(1)
frappe.db.sql("delete from `tabSerial No` where name in ('SN001', 'SN002', 'SN003', 'SN004', 'SN005')") 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") 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", serial_no = frappe.db.get_value("Serial No", "SN001",
["warehouse", "purchase_rate"], as_dict=1) ["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") self.assertEquals(serial_no.warehouse, "_Test Warehouse - _TC")
set_perpetual_inventory(0) 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 = frappe.new_doc("Landed Cost Voucher")
lcv.company = "_Test Company" lcv.company = "_Test Company"
lcv.set("purchase_receipts", [{ lcv.set("purchase_receipts", [{
"receipt_document_type": "Purchase Receipt", "receipt_document_type": receipt_document_type,
"receipt_document": pr.name, "receipt_document": receipt_document,
"supplier": pr.supplier, "supplier": ref_doc.supplier,
"posting_date": pr.posting_date, "posting_date": ref_doc.posting_date,
"grand_total": pr.base_grand_total "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", [{ lcv.set("taxes", [{
"description": "Insurance Charges", "description": "Insurance Charges",
"account": "_Test Account Insurance Charges - _TC", "account": "_Test Account Insurance Charges - _TC",
"amount": 75.0 "amount": 50
}]) }])
lcv.insert() lcv.insert()

View File

@ -21,6 +21,20 @@ frappe.ui.form.on("Purchase Receipt", {
"batch_no": doc.batch_no "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)]]]
}
})
} }
}); });

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cstr, flt, cint from frappe.utils import flt, cint
from frappe import _ from frappe import _
import frappe.defaults import frappe.defaults
@ -81,22 +81,6 @@ class PurchaseReceipt(BuyingController):
if not d.prevdoc_docname: if not d.prevdoc_docname:
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) 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): def get_already_received_qty(self, po, po_detail):
qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item` qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item`
where prevdoc_detail_docname = %s and docstatus = 1 where prevdoc_detail_docname = %s and docstatus = 1
@ -129,19 +113,20 @@ class PurchaseReceipt(BuyingController):
purchase_controller = frappe.get_doc("Purchase Common") purchase_controller = frappe.get_doc("Purchase Common")
# Check for Approving Authority # 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 # Set status as Submitted
frappe.db.set(self, 'status', 'Submitted') frappe.db.set(self, 'status', 'Submitted')
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_ordered_qty()
self.update_billing_status() self.update_billing_status()
if not self.is_return: if not self.is_return:
purchase_controller.update_last_purchase_rate(self, 1) 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() self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit 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') frappe.db.set(self,'status','Cancelled')
self.update_stock_ledger() self.update_prevdoc_status()
self.update_prevdoc_status()
# Must be called after updating received qty in PO
self.update_ordered_qty()
self.update_billing_status() self.update_billing_status()
if not self.is_return: if not self.is_return:
pc_obj.update_last_purchase_rate(self, 0) 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() self.make_gl_entries_on_cancel()
def get_current_stock(self): def get_current_stock(self):
@ -256,13 +239,11 @@ class PurchaseReceipt(BuyingController):
}, warehouse_account[self.supplier_warehouse]["account_currency"])) }, warehouse_account[self.supplier_warehouse]["account_currency"]))
# divisional loss adjustment # divisional loss adjustment
sle_valuation_amount = flt(flt(d.valuation_rate, val_rate_db_precision) * flt(d.qty) * flt(d.conversion_factor), distributed_amount = flt(flt(d.base_net_amount, d.precision("base_net_amount"))) + \
self.precision("base_net_amount", d))
distributed_amount = flt(flt(d.base_net_amount, self.precision("base_net_amount", d))) + \
flt(d.landed_cost_voucher_amount) + flt(d.rm_supp_cost) + flt(d.item_tax_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: if divisional_loss:
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": stock_rbnb, "account": stock_rbnb,