diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 9c19791d29a..61c48c74990 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate +from frappe.utils import nowdate, now_datetime from erpnext.accounts.utils import get_fiscal_year from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError @@ -13,27 +13,28 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ class TestBudget(unittest.TestCase): def test_monthly_budget_crossed_ignore(self): - set_total_expense_zero("2013-02-28", "cost_center") + set_total_expense_zero(nowdate(), "cost_center") budget = make_budget(budget_against="Cost Center") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) + "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) self.assertTrue(frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})) budget.cancel() + jv.cancel() def test_monthly_budget_crossed_stop1(self): - set_total_expense_zero("2013-02-28", "cost_center") + set_total_expense_zero(nowdate(), "cost_center") budget = make_budget(budget_against="Cost Center") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28") + "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate()) self.assertRaises(BudgetError, jv.submit) @@ -41,14 +42,14 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_exception_approver_role(self): - set_total_expense_zero("2013-02-28", "cost_center") + set_total_expense_zero(nowdate(), "cost_center") budget = make_budget(budget_against="Cost Center") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-03-02") + "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate()) self.assertRaises(BudgetError, jv.submit) @@ -112,16 +113,17 @@ class TestBudget(unittest.TestCase): budget.load_from_db() budget.cancel() + po.cancel() def test_monthly_budget_crossed_stop2(self): - set_total_expense_zero("2013-02-28", "project") + set_total_expense_zero(nowdate(), "project") budget = make_budget(budget_against="Project") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-02-28") + "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate()) self.assertRaises(BudgetError, jv.submit) @@ -129,86 +131,76 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_yearly_budget_crossed_stop1(self): - set_total_expense_zero("2013-02-28", "cost_center") + set_total_expense_zero(nowdate(), "cost_center") budget = make_budget(budget_against="Cost Center") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 150000, "_Test Cost Center - _TC", posting_date="2013-03-28") + "_Test Bank - _TC", 250000, "_Test Cost Center - _TC", posting_date=nowdate()) self.assertRaises(BudgetError, jv.submit) budget.cancel() def test_yearly_budget_crossed_stop2(self): - set_total_expense_zero("2013-02-28", "project") + set_total_expense_zero(nowdate(), "project") budget = make_budget(budget_against="Project") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-03-28") + "_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate()) self.assertRaises(BudgetError, jv.submit) budget.cancel() def test_monthly_budget_on_cancellation1(self): - set_total_expense_zero("2013-02-28", "cost_center") + set_total_expense_zero(nowdate(), "cost_center") budget = make_budget(budget_against="Cost Center") - jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) + for i in range(now_datetime().month): + jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", + "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) - self.assertTrue(frappe.db.get_value("GL Entry", - {"voucher_type": "Journal Entry", "voucher_no": jv1.name})) - - jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) - - self.assertTrue(frappe.db.get_value("GL Entry", - {"voucher_type": "Journal Entry", "voucher_no": jv2.name})) + self.assertTrue(frappe.db.get_value("GL Entry", + {"voucher_type": "Journal Entry", "voucher_no": jv.name})) frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") - self.assertRaises(BudgetError, jv1.cancel) + self.assertRaises(BudgetError, jv.cancel) budget.load_from_db() budget.cancel() def test_monthly_budget_on_cancellation2(self): - set_total_expense_zero("2013-02-28", "project") + set_total_expense_zero(nowdate(), "project") budget = make_budget(budget_against="Project") - jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project") + for i in range(now_datetime().month): + jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", + "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project") - self.assertTrue(frappe.db.get_value("GL Entry", - {"voucher_type": "Journal Entry", "voucher_no": jv1.name})) - - jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project") - - self.assertTrue(frappe.db.get_value("GL Entry", - {"voucher_type": "Journal Entry", "voucher_no": jv2.name})) + self.assertTrue(frappe.db.get_value("GL Entry", + {"voucher_type": "Journal Entry", "voucher_no": jv.name})) frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") - self.assertRaises(BudgetError, jv1.cancel) + self.assertRaises(BudgetError, jv.cancel) budget.load_from_db() budget.cancel() def test_monthly_budget_against_group_cost_center(self): - set_total_expense_zero("2013-02-28", "cost_center") - set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC") + set_total_expense_zero(nowdate(), "cost_center") + set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC") budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date="2013-02-28") + "_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date=nowdate()) self.assertRaises(BudgetError, jv.submit) @@ -231,7 +223,7 @@ class TestBudget(unittest.TestCase): frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 40000, cost_center, posting_date="2013-02-28") + "_Test Bank - _TC", 40000, cost_center, posting_date=nowdate()) self.assertRaises(BudgetError, jv.submit) @@ -246,12 +238,14 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again else: budget_against = budget_against_CC or "_Test Cost Center - _TC" + fiscal_year = get_fiscal_year(nowdate())[0] + args = frappe._dict({ "account": "_Test Account Cost for Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", "monthly_end_date": posting_date, "company": "_Test Company", - "fiscal_year": "_Test Fiscal Year 2013", + "fiscal_year": fiscal_year, "budget_against_field": budget_against_field, }) @@ -263,10 +257,10 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again if existing_expense: if budget_against_field == "cost_center": make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) + "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) elif budget_against_field == "project": make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28") + "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate()) def make_budget(**args): args = frappe._dict(args) @@ -274,10 +268,13 @@ def make_budget(**args): budget_against=args.budget_against cost_center=args.cost_center + fiscal_year = get_fiscal_year(nowdate())[0] + if budget_against == "Project": - budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")}) + project_name = "{0}%".format("_Test Project/" + fiscal_year) + budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", project_name)}) else: - cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/_Test Fiscal Year 2013") + cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year) budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)}) for d in budget_list: frappe.db.sql("delete from `tabBudget` where name = %(name)s", d) @@ -290,8 +287,10 @@ def make_budget(**args): else: budget.cost_center =cost_center or "_Test Cost Center - _TC" + monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution") + monthly_distribution.fiscal_year = fiscal_year - budget.fiscal_year = "_Test Fiscal Year 2013" + budget.fiscal_year = fiscal_year budget.monthly_distribution = "_Test Distribution" budget.company = "_Test Company" budget.applicable_on_booking_actual_expenses = 1 @@ -300,7 +299,7 @@ def make_budget(**args): budget.budget_against = budget_against budget.append("accounts", { "account": "_Test Account Cost for Goods Sold - _TC", - "budget_amount": 100000 + "budget_amount": 200000 }) if args.applicable_on_material_request: diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 2214811d8b3..0d753290395 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -30,7 +30,8 @@ "company", "finance_book", "to_rename", - "due_date" + "due_date", + "is_cancelled" ], "fields": [ { @@ -245,12 +246,18 @@ "fieldname": "due_date", "fieldtype": "Date", "label": "Due Date" + }, + { + "default": "0", + "fieldname": "is_cancelled", + "fieldtype": "Check", + "label": "Is Cancelled" } ], "icon": "fa fa-list", "idx": 1, "in_create": 1, - "modified": "2020-03-28 16:22:33.766994", + "modified": "2020-04-07 16:22:33.766994", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 14d05312718..efab5801e8b 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -30,23 +30,20 @@ class GLEntry(Document): self.pl_must_have_cost_center() self.validate_cost_center() - if not self.flags.from_repost: - self.check_pl_account() - self.validate_party() - self.validate_currency() + self.check_pl_account() + self.validate_party() + self.validate_currency() - def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): - if not from_repost: - self.validate_account_details(adv_adj) - self.validate_dimensions_for_pl_and_bs() - check_freezing_date(self.posting_date, adv_adj) + def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'): + self.validate_account_details(adv_adj) + self.validate_dimensions_for_pl_and_bs() validate_frozen_account(self.account, adv_adj) validate_balance_type(self.account, adv_adj) # Update outstanding amt on against voucher if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \ - and self.against_voucher and update_outstanding == 'Yes' and not from_repost: + and self.against_voucher and update_outstanding == 'Yes': update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher) @@ -159,7 +156,6 @@ class GLEntry(Document): if self.party_type and self.party: validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency) - def validate_and_set_fiscal_year(self): if not self.fiscal_year: self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0] @@ -176,19 +172,6 @@ def validate_balance_type(account, adv_adj=False): (balance_must_be=="Credit" and flt(balance) > 0): frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be))) -def check_freezing_date(posting_date, adv_adj=False): - """ - Nobody can do GL Entries where posting date is before freezing date - except authorized person - """ - if not adv_adj: - acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') - if acc_frozen_upto: - frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') - if getdate(posting_date) <= getdate(acc_frozen_upto) \ - and not frozen_accounts_modifier in frappe.get_roles(): - frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) - def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False): if party_type and party: party_condition = " and party_type={0} and party={1}"\ diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index eb3017a46b5..d6ffdb69ed2 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -57,6 +57,7 @@ class JournalEntry(AccountsController): from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_salary_slip(self.name) + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') self.make_gl_entries(1) self.update_advance_paid() self.update_expense_claim() @@ -594,7 +595,7 @@ class JournalEntry(AccountsController): for d in self.accounts: if d.reference_type=="Expense Claim" and d.reference_name: doc = frappe.get_doc("Expense Claim", d.reference_name) - update_reimbursed_amount(doc) + update_reimbursed_amount(doc, jv=self.name) def validate_expense_claim(self): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index b53e68ff735..8b7c0960334 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -75,6 +75,7 @@ class PaymentEntry(AccountsController): self.set_status() def on_cancel(self): + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') self.setup_party_account_field() self.make_gl_entries(cancel=1) self.update_outstanding_amounts() @@ -571,7 +572,7 @@ class PaymentEntry(AccountsController): for d in self.get("references"): if d.reference_doctype=="Expense Claim" and d.reference_name: doc = frappe.get_doc("Expense Claim", d.reference_name) - update_reimbursed_amount(doc) + update_reimbursed_amount(doc, self.name) def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 4c7d933476f..8bb741f0b25 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -35,8 +35,6 @@ class TestPaymentEntry(unittest.TestCase): pe.cancel() - self.assertFalse(self.get_gle(pe.name)) - so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") self.assertEqual(so_advance_paid, 0) @@ -124,7 +122,6 @@ class TestPaymentEntry(unittest.TestCase): self.assertEqual(outstanding_amount, 0) pe.cancel() - self.assertFalse(self.get_gle(pe.name)) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) self.assertEqual(outstanding_amount, 100) @@ -381,7 +378,6 @@ class TestPaymentEntry(unittest.TestCase): self.assertEqual(outstanding_amount, 0) pe3.cancel() - self.assertFalse(self.get_gle(pe3.name)) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount")) self.assertEqual(outstanding_amount, -100) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js index 03b8f932a9e..87e02fef1b1 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js @@ -5,7 +5,7 @@ frappe.ui.form.on('Period Closing Voucher', { onload: function(frm) { if (!frm.doc.transaction_date) frm.doc.transaction_date = frappe.datetime.obj_to_str(new Date()); }, - + setup: function(frm) { frm.set_query("closing_account_head", function() { return { @@ -18,9 +18,9 @@ frappe.ui.form.on('Period Closing Voucher', { } }); }, - + refresh: function(frm) { - if(frm.doc.docstatus==1) { + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { "voucher_no": frm.doc.name, @@ -33,5 +33,5 @@ frappe.ui.form.on('Period Closing Voucher', { }, "fa fa-table"); } } - + }) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index eb95e458dc8..0bd9a90b3ee 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -17,8 +17,9 @@ class PeriodClosingVoucher(AccountsController): self.make_gl_entries() def on_cancel(self): - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.name) + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + from erpnext.accounts.general_ledger import make_reverse_gl_entries + make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name, cancel=True) def validate_account_head(self): closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type") diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 3cf4d5994a5..4f6be59c65e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -382,11 +382,6 @@ function hide_fields(doc) { cur_frm.refresh_fields(); } -cur_frm.cscript.update_stock = function(doc, dt, dn) { - hide_fields(doc, dt, dn); - this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false) -} - cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { return { filters: [ @@ -528,5 +523,10 @@ frappe.ui.form.on("Purchase Invoice", { erpnext.buying.get_default_bom(frm); } frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); + }, + + update_stock: function(frm) { + hide_fields(frm.doc); + frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false); } }) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index b1ae194301c..3aa24df16d3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -14,7 +14,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 from erpnext.stock import get_warehouse_account_map -from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries +from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, make_reverse_gl_entries from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center @@ -382,7 +382,7 @@ class PurchaseInvoice(BuyingController): self.update_project() update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) - def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): + def make_gl_entries(self, gl_entries=None): if not self.grand_total: return if not gl_entries: @@ -391,21 +391,17 @@ class PurchaseInvoice(BuyingController): if 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, from_repost=from_repost) + if self.docstatus == 1: + make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False) + elif self.docstatus == 2: + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) if update_outstanding == "No": update_outstanding_amt(self.credit_to, "Supplier", self.supplier, self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) - if (repost_future_gle or self.flags.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, company = self.company) - 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) + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) def get_gl_entries(self, warehouse_account=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) @@ -934,6 +930,7 @@ class PurchaseInvoice(BuyingController): frappe.db.set(self, 'status', 'Cancelled') unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') def update_project(self): project_list = [] diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json index 171927c1822..7030faf2b73 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_records.json +++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json @@ -138,13 +138,12 @@ "row_id": 7 } ], - "posting_date": "2013-02-03", "supplier": "_Test Supplier", "supplier_name": "_Test Supplier" }, - - - + + + { "bill_no": "NA", "buying_price_list": "_Test Price List", @@ -204,7 +203,6 @@ "tax_amount": 150.0 } ], - "posting_date": "2013-02-03", "supplier": "_Test Supplier", "supplier_name": "_Test Supplier" } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 60e41f95536..f248276e5bb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -345,7 +345,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte set_dynamic_labels: function() { this._super(); - this.hide_fields(this.frm.doc); + this.frm.events.hide_fields(this.frm) }, items_on_form_rendered: function() { @@ -404,7 +404,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte if(r.message && r.message.print_format) { me.frm.pos_print_format = r.message.print_format; } - me.frm.script_manager.trigger("update_stock"); + me.frm.trigger("update_stock"); if(me.frm.doc.taxes_and_charges) { me.frm.script_manager.trigger("taxes_and_charges"); } @@ -446,35 +446,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte // for backward compatibility: combine new and previous states $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm})); -// Hide Fields -// ------------ -cur_frm.cscript.hide_fields = function(doc) { - var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances', - 'advances', 'from_date', 'to_date']; - - if(cint(doc.is_pos) == 1) { - hide_field(parent_fields); - } else { - for (var i in parent_fields) { - var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]]; - if(!docfield.hidden) unhide_field(parent_fields[i]); - } - } - - // India related fields - if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']); - else hide_field(['c_form_applicable', 'c_form_no']); - - this.frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically)); - - cur_frm.refresh_fields(); -} - -cur_frm.cscript.update_stock = function(doc, dt, dn) { - cur_frm.cscript.hide_fields(doc, dt, dn); - this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false) -} - cur_frm.cscript['Make Delivery Note'] = function() { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_delivery_note", @@ -719,6 +690,12 @@ frappe.ui.form.on('Sales Invoice', { frm.redemption_conversion_factor = null; }, + update_stock: function(frm, dt, dn) { + frm.events.hide_fields(frm); + frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock); + frm.trigger('reset_posting_time'); + }, + redeem_loyalty_points: function(frm) { frm.events.get_loyalty_details(frm); }, @@ -742,6 +719,29 @@ frappe.ui.form.on('Sales Invoice', { } }, + hide_fields: function(frm) { + let doc = frm.doc; + var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances', + 'advances', 'from_date', 'to_date']; + + if(cint(doc.is_pos) == 1) { + hide_field(parent_fields); + } else { + for (var i in parent_fields) { + var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]]; + if(!docfield.hidden) unhide_field(parent_fields[i]); + } + } + + // India related fields + if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']); + else hide_field(['c_form_applicable', 'c_form_no']); + + frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically)); + + frm.refresh_fields(); + }, + get_loyalty_details: function(frm) { if (frm.doc.customer && frm.doc.redeem_loyalty_points) { frappe.call({ diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3c40112ae6f..3b0fade0e53 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -7,7 +7,6 @@ import frappe.defaults from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date -from erpnext.controllers.stock_controller import update_gl_entries_after from frappe.model.mapper import get_mapped_doc from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option @@ -282,6 +281,8 @@ class SalesInvoice(SellingController): if "Healthcare" in active_domains: manage_invoice_submit_cancel(self, "on_cancel") + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + def update_status_updater_args(self): if cint(self.update_stock): self.status_updater.append({ @@ -717,7 +718,9 @@ class SalesInvoice(SellingController): if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) - def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): + def make_gl_entries(self, gl_entries=None): + from erpnext.accounts.general_ledger import make_reverse_gl_entries + auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) if not gl_entries: gl_entries = self.get_gl_entries() @@ -729,23 +732,19 @@ class SalesInvoice(SellingController): update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or cint(self.redeem_loyalty_points)) else "Yes" - make_gl_entries(gl_entries, cancel=(self.docstatus == 2), - update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) + if self.docstatus == 1: + make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False) + elif self.docstatus == 2: + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) if update_outstanding == "No": from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt update_outstanding_amt(self.debit_to, "Customer", self.customer, self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) - if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) \ - and cint(auto_accounting_for_stock): - items, warehouses = self.get_items_and_warehouses() - update_gl_entries_after(self.posting_date, self.posting_time, - warehouses, items, company = self.company) elif self.docstatus == 2 and cint(self.update_stock) \ and cint(auto_accounting_for_stock): - from erpnext.accounts.general_ledger import delete_gl_entries - delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import merge_similar_entries diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 88b54fec8f2..dd727a49b08 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -364,7 +364,7 @@ class TestSalesInvoice(unittest.TestCase): gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - self.assertFalse(gle) + self.assertTrue(gle) def test_tax_calculation_with_multiple_items(self): si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) @@ -678,14 +678,15 @@ class TestSalesInvoice(unittest.TestCase): gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - self.assertFalse(gle) + self.assertTrue(gle) def test_pos_gl_entry_with_perpetual_inventory(self): make_pos_profile() - pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") - pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", + income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pos.is_pos = 1 pos.update_stock = 1 @@ -766,9 +767,13 @@ class TestSalesInvoice(unittest.TestCase): def test_pos_change_amount(self): make_pos_profile() - pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", + item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1") - pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", + debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", + income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", + cost_center = "Main - TCP1", do_not_save=True) pos.is_pos = 1 pos.update_stock = 1 @@ -787,8 +792,15 @@ class TestSalesInvoice(unittest.TestCase): from erpnext.accounts.doctype.sales_invoice.pos import make_invoice pos_profile = make_pos_profile() - pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") - pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) + + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", + item_code= "_Test FG Item", + warehouse= "Stores - TCP1", cost_center= "Main - TCP1") + + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", + debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", + income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", + cost_center = "Main - TCP1", do_not_save=True) pos.is_pos = 1 pos.update_stock = 1 @@ -891,11 +903,9 @@ class TestSalesInvoice(unittest.TestCase): gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - self.assertFalse(gle) - + self.assertTrue(gle) frappe.db.sql("delete from `tabPOS Profile`") - si.delete() def test_pos_si_without_payment(self): set_perpetual_inventory() @@ -1012,9 +1022,6 @@ class TestSalesInvoice(unittest.TestCase): si.cancel() - self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` - where reference_name=%s""", si.name)) - def test_serialized(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -1230,7 +1237,7 @@ class TestSalesInvoice(unittest.TestCase): gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - self.assertFalse(gle) + self.assertTrue(gle) def test_invalid_currency(self): # Customer currency = USD diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 5ba455c1315..fb1a4f4dba2 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import flt, cstr, cint, comma_and +from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now from frappe import _ from erpnext.accounts.utils import get_stock_and_account_balance from frappe.model.meta import get_field_precision @@ -15,17 +15,17 @@ class ClosedAccountingPeriod(frappe.ValidationError): pass class StockAccountInvalidTransaction(frappe.ValidationError): pass class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass -def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): +def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'): if gl_map: if not cancel: validate_accounting_period(gl_map) gl_map = process_gl_map(gl_map, merge_entries) if gl_map and len(gl_map) > 1: - save_entries(gl_map, adv_adj, update_outstanding, from_repost) + save_entries(gl_map, adv_adj, update_outstanding) else: frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.")) else: - delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) + make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) def validate_accounting_period(gl_map): accounting_periods = frappe.db.sql(""" SELECT @@ -119,33 +119,36 @@ def check_if_in_list(gle, gl_map, dimensions=None): if same_head: return e -def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): - if not from_repost: - validate_cwip_accounts(gl_map) +def save_entries(gl_map, adv_adj, update_outstanding): + validate_cwip_accounts(gl_map) round_off_debit_credit(gl_map) + + if gl_map: + check_freezing_date(gl_map[0]["posting_date"], adv_adj) + for entry in gl_map: - make_entry(entry, adv_adj, update_outstanding, from_repost) + make_entry(entry, adv_adj, update_outstanding) # check against budget - if not from_repost: - validate_expense_against_budget(entry) + validate_expense_against_budget(entry) - if not from_repost: - validate_account_for_perpetual_inventory(gl_map) + validate_account_for_perpetual_inventory(gl_map) -def make_entry(args, adv_adj, update_outstanding, from_repost=False): +def make_entry(args, adv_adj, update_outstanding): gle = frappe.new_doc("GL Entry") gle.update(args) gle.flags.ignore_permissions = 1 - gle.flags.from_repost = from_repost gle.validate() gle.db_insert() - gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) + gle.run_method("on_update_with_args", adv_adj, update_outstanding) gle.flags.ignore_validate = True gle.submit() + # check against budget + validate_expense_against_budget(args) + def validate_account_for_perpetual_inventory(gl_map): if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): account_list = [gl_entries.account for gl_entries in gl_map] @@ -169,33 +172,33 @@ def validate_account_for_perpetual_inventory(gl_map): .format(account), StockAccountInvalidTransaction) # This has been comment for a temporary, will add this code again on release of immutable ledger - # elif account_bal != stock_bal: - # precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), - # currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) + elif account_bal != stock_bal: + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), + currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) - # diff = flt(stock_bal - account_bal, precision) - # error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( - # stock_bal, account_bal, frappe.bold(account)) - # error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) - # stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") + diff = flt(stock_bal - account_bal, precision) + error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( + stock_bal, account_bal, frappe.bold(account)) + error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) + stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") - # db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') - # db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') + db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') + db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') - # journal_entry_args = { - # 'accounts':[ - # {'account': account, db_or_cr_warehouse_account : abs(diff)}, - # {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] - # } + journal_entry_args = { + 'accounts':[ + {'account': account, db_or_cr_warehouse_account : abs(diff)}, + {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] + } - # frappe.msgprint(msg="""{0}

{1}

""".format(error_reason, error_resolution), - # raise_exception=StockValueAndAccountBalanceOutOfSync, - # title=_('Values Out Of Sync'), - # primary_action={ - # 'label': _('Make Journal Entry'), - # 'client_action': 'erpnext.route_to_adjustment_jv', - # 'args': journal_entry_args - # }) + frappe.msgprint(msg="""{0}

{1}

""".format(error_reason, error_resolution), + raise_exception=StockValueAndAccountBalanceOutOfSync, + title=_('Values Out Of Sync'), + primary_action={ + 'label': _('Make Journal Entry'), + 'client_action': 'erpnext.route_to_adjustment_jv', + 'args': journal_entry_args + }) def validate_cwip_accounts(gl_map): cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) @@ -282,31 +285,64 @@ def get_round_off_account_and_cost_center(company): return round_off_account, round_off_cost_center -def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, - adv_adj=False, update_outstanding="Yes"): - - from erpnext.accounts.doctype.gl_entry.gl_entry import validate_balance_type, \ - check_freezing_date, update_outstanding_amt, validate_frozen_account +def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, + adv_adj=False, update_outstanding="Yes"): + """ + Get original gl entries of the voucher + and make reverse gl entries by swapping debit and credit + """ if not gl_entries: - gl_entries = frappe.db.sql(""" - select account, posting_date, party_type, party, cost_center, fiscal_year,voucher_type, - voucher_no, against_voucher_type, against_voucher, cost_center, company - from `tabGL Entry` - where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True) + gl_entries = frappe.get_all("GL Entry", + fields = ["*"], + filters = { + "voucher_type": voucher_type, + "voucher_no": voucher_no + }) if gl_entries: + set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no']) check_freezing_date(gl_entries[0]["posting_date"], adv_adj) - frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", - (voucher_type or gl_entries[0]["voucher_type"], voucher_no or gl_entries[0]["voucher_no"])) + for entry in gl_entries: + entry['name'] = None + debit = entry.get('debit', 0) + credit = entry.get('credit', 0) - for entry in gl_entries: - validate_frozen_account(entry["account"], adv_adj) - validate_balance_type(entry["account"], adv_adj) - if not adv_adj: - validate_expense_against_budget(entry) + debit_in_account_currency = entry.get('debit_in_account_currency', 0) + credit_in_account_currency = entry.get('credit_in_account_currency', 0) - if entry.get("against_voucher") and update_outstanding == 'Yes' and not adv_adj: - update_outstanding_amt(entry["account"], entry.get("party_type"), entry.get("party"), entry.get("against_voucher_type"), - entry.get("against_voucher"), on_cancel=True) + entry['debit'] = credit + entry['credit'] = debit + entry['debit_in_account_currency'] = credit_in_account_currency + entry['credit_in_account_currency'] = debit_in_account_currency + + entry['remarks'] = "On cancellation of " + entry['voucher_no'] + entry['is_cancelled'] = 1 + entry['posting_date'] = today() + + if entry['debit'] or entry['credit']: + make_entry(entry, adv_adj, "Yes") + + +def check_freezing_date(posting_date, adv_adj=False): + """ + Nobody can do GL Entries where posting date is before freezing date + except authorized person + """ + if not adv_adj: + acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') + if acc_frozen_upto: + frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') + if getdate(posting_date) <= getdate(acc_frozen_upto) \ + and not frozen_accounts_modifier in frappe.get_roles(): + frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) + +def set_as_cancel(voucher_type, voucher_no): + """ + Set is_cancelled=1 in all original gl entries for the voucher + """ + frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1, + modified=%s, modified_by=%s + where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", + (now(), frappe.session.user, voucher_type, voucher_no)) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index ac49d373d47..1188beaa0f8 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -154,8 +154,12 @@ frappe.query_reports["General Ledger"] = { { "fieldname": "include_default_book_entries", "label": __("Include Default Book Entries"), - "fieldtype": "Check", - "default": 1 + "fieldtype": "Check" + }, + { + "fieldname": "show_cancelled_entries", + "label": __("Show Cancelled Entries"), + "fieldtype": "Check" } ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index f776d933014..7af5fa8eaa7 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -188,6 +188,9 @@ def get_conditions(filters): else: conditions.append("finance_book in (%(finance_book)s)") + if not filters.get("show_cancelled_entries"): + conditions.append("is_cancelled = 0") + from frappe.desk.reportview import build_match_conditions match_conditions = build_match_conditions("GL Entry") diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 4789063ba50..b5d6ca9bbce 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -897,4 +897,60 @@ def get_stock_accounts(company): return frappe.get_all("Account", filters = { "account_type": "Stock", "company": company - }) \ No newline at end of file + }) + +def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, + warehouse_account=None, company=None): + def _delete_gl_entries(voucher_type, voucher_no): + frappe.db.sql("""delete from `tabGL Entry` + where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) + + if not warehouse_account: + warehouse_account = get_warehouse_account_map(company) + + future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items) + gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) + + for voucher_type, voucher_no in future_stock_vouchers: + existing_gle = gle.get((voucher_type, voucher_no), []) + voucher_obj = frappe.get_doc(voucher_type, voucher_no) + expected_gle = voucher_obj.get_gl_entries(warehouse_account) + if expected_gle: + if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): + _delete_gl_entries(voucher_type, voucher_no) + voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True) + else: + _delete_gl_entries(voucher_type, voucher_no) + +def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None): + future_stock_vouchers = [] + + values = [] + condition = "" + if for_items: + condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items))) + values += for_items + + if for_warehouses: + condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses))) + values += for_warehouses + + for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no + from `tabStock Ledger Entry` sle + where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} + order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), + tuple([posting_date, posting_time] + values), as_dict=True): + future_stock_vouchers.append([d.voucher_type, d.voucher_no]) + + return future_stock_vouchers + +def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): + gl_entries = {} + if future_stock_vouchers: + for d in frappe.db.sql("""select * from `tabGL Entry` + where posting_date >= %s and voucher_no in (%s)""" % + ('%s', ', '.join(['%s']*len(future_stock_vouchers))), + tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): + gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) + + return gl_entries \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 759d42a5427..ecbfeb7f140 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -11,7 +11,7 @@ from frappe.model.document import Document from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_depreciation_accounts -from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries +from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries from erpnext.accounts.utils import get_account_currency from erpnext.controllers.accounts_controller import AccountsController @@ -41,7 +41,8 @@ class Asset(AccountsController): self.validate_cancellation() self.delete_depreciation_entries() self.set_status() - delete_gl_entries(voucher_type='Asset', voucher_no=self.name) + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + make_reverse_gl_entries(voucher_type='Asset', voucher_no=self.name) self.db_set('booked_fixed_asset', 0) def validate_asset_and_reference(self): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a56440de3d3..050b30d89a8 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -66,9 +66,6 @@ class TestAsset(unittest.TestCase): pr.cancel() self.assertEqual(asset.docstatus, 2) - self.assertFalse(frappe.db.get_value("GL Entry", - {"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) - def test_is_fixed_asset_set(self): asset = create_asset(is_existing_asset = 1) doc = frappe.new_doc('Purchase Invoice') diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3e97f76f7b9..eecb143d556 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -664,23 +664,26 @@ class AccountsController(TransactionBase): def set_total_advance_paid(self): if self.doctype == "Sales Order": dr_or_cr = "credit_in_account_currency" + rev_dr_or_cr = "debit_in_account_currency" party = self.customer else: dr_or_cr = "debit_in_account_currency" + rev_dr_or_cr = "credit_in_account_currency" party = self.supplier advance = frappe.db.sql(""" select - account_currency, sum({dr_or_cr}) as amount + account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount from `tabGL Entry` where against_voucher_type = %s and against_voucher = %s and party=%s and docstatus = 1 - """.format(dr_or_cr=dr_or_cr), (self.doctype, self.name, party), as_dict=1) + """.format(dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr), (self.doctype, self.name, party), as_dict=1) #nosec if advance: advance = advance[0] + advance_paid = flt(advance.amount, self.precision("advance_paid")) formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 9d453af2ace..86de80815db 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -7,7 +7,7 @@ from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate from frappe import _ 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.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock import get_warehouse_account_map @@ -23,9 +23,9 @@ class StockController(AccountsController): self.validate_serialized_batch() self.validate_customer_provided_item() - def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): + def make_gl_entries(self, gl_entries=None): if self.docstatus == 2: - delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) if cint(erpnext.is_perpetual_inventory_enabled(self.company)): warehouse_account = get_warehouse_account_map(self.company) @@ -33,16 +33,12 @@ class StockController(AccountsController): if self.docstatus==1: if not gl_entries: gl_entries = self.get_gl_entries(warehouse_account) - make_gl_entries(gl_entries, from_repost=from_repost) + make_gl_entries(gl_entries) - if (repost_future_gle or self.flags.repost_future_gle): - items, warehouses = self.get_items_and_warehouses() - update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items, - warehouse_account, company=self.company) elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1: gl_entries = [] gl_entries = self.get_asset_gl_entry(gl_entries) - make_gl_entries(gl_entries, from_repost=from_repost) + make_gl_entries(gl_entries) def validate_serialized_batch(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -274,21 +270,21 @@ class StockController(AccountsController): "batch_no": cstr(d.get("batch_no")).strip(), "serial_no": d.get("serial_no"), "project": d.get("project") or self.get('project'), - "is_cancelled": self.docstatus==2 and "Yes" or "No" + "is_cancelled": 1 if self.docstatus==2 else 0 }) sl_dict.update(args) return sl_dict - def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False, + def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False): from erpnext.stock.stock_ledger import make_sl_entries - make_sl_entries(sl_entries, is_amended, allow_negative_stock, via_landed_cost_voucher) + make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher) - def make_gl_entries_on_cancel(self, repost_future_gle=True): + def make_gl_entries_on_cancel(self): if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)): - self.make_gl_entries(repost_future_gle=repost_future_gle) + self.make_gl_entries() def get_serialized_items(self): serialized_items = [] @@ -391,29 +387,6 @@ class StockController(AccountsController): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): d.allow_zero_valuation_rate = 1 -def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, - warehouse_account=None, company=None): - def _delete_gl_entries(voucher_type, voucher_no): - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) - - if not warehouse_account: - warehouse_account = get_warehouse_account_map(company) - - future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items) - gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) - - for voucher_type, voucher_no in future_stock_vouchers: - existing_gle = gle.get((voucher_type, voucher_no), []) - voucher_obj = frappe.get_doc(voucher_type, voucher_no) - expected_gle = voucher_obj.get_gl_entries(warehouse_account) - if expected_gle: - if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): - _delete_gl_entries(voucher_type, voucher_no) - voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True) - else: - _delete_gl_entries(voucher_type, voucher_no) - def compare_existing_and_expected_gle(existing_gle, expected_gle): matched = True for entry in expected_gle: @@ -430,36 +403,3 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle): matched = False break return matched - -def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None): - future_stock_vouchers = [] - - values = [] - condition = "" - if for_items: - condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items))) - values += for_items - - if for_warehouses: - condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses))) - values += for_warehouses - - for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no - from `tabStock Ledger Entry` sle - where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} - order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), - tuple([posting_date, posting_time] + values), as_dict=True): - future_stock_vouchers.append([d.voucher_type, d.voucher_no]) - - return future_stock_vouchers - -def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): - gl_entries = {} - if future_stock_vouchers: - for d in frappe.db.sql("""select * from `tabGL Entry` - where posting_date >= %s and voucher_no in (%s)""" % - ('%s', ', '.join(['%s']*len(future_stock_vouchers))), - tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): - gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) - - return gl_entries diff --git a/erpnext/education/doctype/fees/fees.py b/erpnext/education/doctype/fees/fees.py index f31003bf326..01f7b872494 100644 --- a/erpnext/education/doctype/fees/fees.py +++ b/erpnext/education/doctype/fees/fees.py @@ -10,7 +10,7 @@ from frappe.utils import money_in_words from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from frappe.utils.csvutils import getlink from erpnext.controllers.accounts_controller import AccountsController -from erpnext.accounts.general_ledger import delete_gl_entries +from erpnext.accounts.general_ledger import make_reverse_gl_entries class Fees(AccountsController): @@ -81,7 +81,8 @@ class Fees(AccountsController): frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name))) def on_cancel(self): - delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name, cancel=True) # frappe.db.set(self, 'status', 'Cancelled') diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index ad9d86b66e2..ac1bfa1a391 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -76,6 +76,7 @@ class ExpenseClaim(AccountsController): def on_cancel(self): self.update_task_and_project() + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') if self.payable_account: self.make_gl_entries(cancel=True) @@ -260,10 +261,17 @@ class ExpenseClaim(AccountsController): if not expense.default_account or not validate: expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"] -def update_reimbursed_amount(doc): - amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt +def update_reimbursed_amount(doc, jv=None): + + condition = "" + + if jv: + condition += "and voucher_no = '{0}'".format(jv) + + amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) - ifnull(sum(credit_in_account_currency), 0)as amt from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s - and party = %s """, (doc.name, doc.employee) ,as_dict=1)[0].amt + and party = %s {condition}""".format(condition=condition), #nosec + (doc.name, doc.employee) ,as_dict=1)[0].amt doc.total_amount_reimbursed = amt frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 26f580db339..ca67d71bb0c 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -62,9 +62,9 @@ class TestProductionPlan(unittest.TestCase): def test_production_plan_for_existing_ordered_qty(self): sr1 = create_stock_reconciliation(item_code="Raw Material Item 1", - target="_Test Warehouse - _TC", qty=1, rate=100) + target="_Test Warehouse - _TC", qty=1, rate=110) sr2 = create_stock_reconciliation(item_code="Raw Material Item 2", - target="_Test Warehouse - _TC", qty=1, rate=100) + target="_Test Warehouse - _TC", qty=1, rate=120) pln = create_production_plan(item_code='Test Production Item 1', ignore_existing_ordered_qty=0) self.assertTrue(len(pln.mr_items), 1) @@ -86,9 +86,9 @@ class TestProductionPlan(unittest.TestCase): def test_production_plan_without_multi_level_for_existing_ordered_qty(self): sr1 = create_stock_reconciliation(item_code="Raw Material Item 1", - target="_Test Warehouse - _TC", qty=1, rate=100) + target="_Test Warehouse - _TC", qty=1, rate=130) sr2 = create_stock_reconciliation(item_code="Subassembly Item 1", - target="_Test Warehouse - _TC", qty=1, rate=100) + target="_Test Warehouse - _TC", qty=1, rate=140) pln = create_production_plan(item_code='Test Production Item 1', use_multi_level_bom=0, ignore_existing_ordered_qty=0) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a216f53a8ba..b903bcae91c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -1,6 +1,7 @@ execute:import unidecode # new requirement erpnext.patches.v8_0.move_perpetual_inventory_setting erpnext.patches.v8_9.set_print_zero_amount_taxes +erpnext.patches.v12_0.update_is_cancelled_field erpnext.patches.v11_0.rename_production_order_to_work_order erpnext.patches.v11_0.refactor_naming_series erpnext.patches.v11_0.refactor_autoname_naming @@ -261,7 +262,6 @@ erpnext.patches.v6_19.comment_feed_communication erpnext.patches.v6_21.fix_reorder_level erpnext.patches.v6_21.rename_material_request_fields erpnext.patches.v6_23.update_stopped_status_to_closed -erpnext.patches.v6_24.repost_valuation_rate_for_serialized_items erpnext.patches.v6_24.set_recurring_id erpnext.patches.v6_20x.set_compact_print execute:frappe.delete_doc_if_exists("Web Form", "contact") #2016-03-10 @@ -315,7 +315,6 @@ erpnext.patches.v7_0.set_material_request_type_in_item erpnext.patches.v7_0.rename_examination_to_assessment erpnext.patches.v7_0.set_portal_settings erpnext.patches.v7_0.update_change_amount_account -erpnext.patches.v7_0.repost_future_gle_for_purchase_invoice erpnext.patches.v7_0.fix_duplicate_icons erpnext.patches.v7_0.repost_gle_for_pos_sales_return erpnext.patches.v7_1.update_total_billing_hours diff --git a/erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py b/erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py index 68c06ef62b2..e6546e386b9 100644 --- a/erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py +++ b/erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py @@ -24,9 +24,9 @@ def execute(): doc = frappe.get_doc("Purchase Receipt", d.name) doc.docstatus = 2 - doc.make_gl_entries_on_cancel(repost_future_gle=False) + doc.make_gl_entries_on_cancel() # update gl entries for submit state of PR doc.docstatus = 1 - doc.make_gl_entries(repost_future_gle=False) + doc.make_gl_entries() diff --git a/erpnext/patches/v10_0/taxes_issue_with_pos.py b/erpnext/patches/v10_0/taxes_issue_with_pos.py index 9b54297e220..2a3275ac2ce 100644 --- a/erpnext/patches/v10_0/taxes_issue_with_pos.py +++ b/erpnext/patches/v10_0/taxes_issue_with_pos.py @@ -19,7 +19,7 @@ def execute(): doc.db_update() delete_gle_for_voucher(doc.name) - doc.make_gl_entries(repost_future_gle=False) + doc.make_gl_entries() def delete_gle_for_voucher(voucher_no): frappe.db.sql("""delete from `tabGL Entry` where voucher_no = %(voucher_no)s""", diff --git a/erpnext/patches/v12_0/set_total_batch_quantity.py b/erpnext/patches/v12_0/set_total_batch_quantity.py index d373275c253..7296eaa33d8 100644 --- a/erpnext/patches/v12_0/set_total_batch_quantity.py +++ b/erpnext/patches/v12_0/set_total_batch_quantity.py @@ -6,6 +6,6 @@ def execute(): for batch in frappe.get_all("Batch", fields=["name", "batch_id"]): batch_qty = frappe.db.get_value("Stock Ledger Entry", - {"docstatus": 1, "batch_no": batch.batch_id, "is_cancelled": "No"}, + {"docstatus": 1, "batch_no": batch.batch_id, "is_cancelled": 0}, "sum(actual_qty)") or 0.0 frappe.db.set_value("Batch", batch.name, "batch_qty", batch_qty, update_modified=False) diff --git a/erpnext/patches/v12_0/update_is_cancelled_field.py b/erpnext/patches/v12_0/update_is_cancelled_field.py new file mode 100644 index 00000000000..0b2e82750b2 --- /dev/null +++ b/erpnext/patches/v12_0/update_is_cancelled_field.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + try: + frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')") + frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')") + + frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 1 where is_cancelled = 'Yes'") + frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 1 where is_cancelled = 'Yes'") + + frappe.reload_doc("stock", "doctype", "stock_ledger_entry") + frappe.reload_doc("stock", "doctype", "serial_no") + except: + pass \ No newline at end of file diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py index 16932af3d66..c6c94d41797 100644 --- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py +++ b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py @@ -8,7 +8,7 @@ from frappe.utils import flt def execute(): from erpnext.stock.stock_balance import repost repost(allow_zero_rate=True, only_actual=True) - + frappe.reload_doctype("Account") warehouse_account = frappe.db.sql("""select name, master_name from tabAccount @@ -43,7 +43,7 @@ def execute(): where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) voucher = frappe.get_doc(voucher_type, voucher_no) - voucher.make_gl_entries(repost_future_gle=False) + voucher.make_gl_entries() frappe.db.commit() except Exception as e: print(frappe.get_traceback()) diff --git a/erpnext/patches/v6_24/repost_valuation_rate_for_serialized_items.py b/erpnext/patches/v6_24/repost_valuation_rate_for_serialized_items.py deleted file mode 100644 index 3b157a3e365..00000000000 --- a/erpnext/patches/v6_24/repost_valuation_rate_for_serialized_items.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import today -from erpnext.accounts.utils import get_fiscal_year -from erpnext.stock.stock_ledger import update_entries_after - -def execute(): - try: - year_start_date = get_fiscal_year(today())[1] - except: - return - - if year_start_date: - items = frappe.db.sql("""select distinct item_code, warehouse from `tabStock Ledger Entry` - where ifnull(serial_no, '') != '' and actual_qty > 0 and incoming_rate=0""", as_dict=1) - - for d in items: - try: - update_entries_after({ - "item_code": d.item_code, - "warehouse": d.warehouse, - "posting_date": year_start_date - }, allow_zero_rate=True) - except: - pass \ No newline at end of file diff --git a/erpnext/patches/v7_0/repost_future_gle_for_purchase_invoice.py b/erpnext/patches/v7_0/repost_future_gle_for_purchase_invoice.py deleted file mode 100644 index 9e21fb699b9..00000000000 --- a/erpnext/patches/v7_0/repost_future_gle_for_purchase_invoice.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cint -from erpnext.stock import get_warehouse_account_map -from erpnext.controllers.stock_controller import update_gl_entries_after - -def execute(): - company_list = frappe.db.sql_list("""Select name from tabCompany where enable_perpetual_inventory = 1""") - frappe.reload_doc('accounts', 'doctype', 'sales_invoice') - - frappe.reload_doctype("Purchase Invoice") - wh_account = get_warehouse_account_map() - - for pi in frappe.get_all("Purchase Invoice", fields=["name", "company"], filters={"docstatus": 1, "update_stock": 1}): - if pi.company in company_list: - pi_doc = frappe.get_doc("Purchase Invoice", pi.name) - items, warehouses = pi_doc.get_items_and_warehouses() - update_gl_entries_after(pi_doc.posting_date, pi_doc.posting_time, - warehouses, items, wh_account, company = pi.company) - - frappe.db.commit() \ No newline at end of file diff --git a/erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py b/erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py index 2d1a15181b6..b864e597b82 100644 --- a/erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py +++ b/erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py @@ -8,13 +8,13 @@ from frappe.utils import cint def execute(): frappe.reload_doctype("Purchase Invoice") - for pi in frappe.db.sql("""select name from `tabPurchase Invoice` - where company in(select name from tabCompany where enable_perpetual_inventory = 1) and + for pi in frappe.db.sql("""select name from `tabPurchase Invoice` + where company in(select name from tabCompany where enable_perpetual_inventory = 1) and update_stock=1 and docstatus=1 order by posting_date asc""", as_dict=1): - - frappe.db.sql("""delete from `tabGL Entry` + + frappe.db.sql("""delete from `tabGL Entry` where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name) - + pi_doc = frappe.get_doc("Purchase Invoice", pi.name) - pi_doc.make_gl_entries(repost_future_gle=False) + pi_doc.make_gl_entries() frappe.db.commit() \ No newline at end of file diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index 1c12c352ed4..2ce49e766b9 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -50,7 +50,7 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({ show_stock_ledger: function() { var me = this; - if(this.frm.doc.docstatus===1) { + if(this.frm.doc.docstatus > 0) { cur_frm.add_custom_button(__("Stock Ledger"), function() { frappe.route_options = { voucher_no: me.frm.doc.name, @@ -66,7 +66,7 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({ show_general_ledger: function() { var me = this; - if(this.frm.doc.docstatus===1) { + if(this.frm.doc.docstatus > 0) { cur_frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { voucher_no: me.frm.doc.name, diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index d8e9a635b3a..b8b0d404e50 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -335,7 +335,7 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.get("items")[-1].qty, 7) self.assertEqual(so.get("items")[-1].amount, 1400) self.assertEqual(so.status, 'To Deliver and Bill') - + def test_remove_item_in_update_child_qty_rate(self): so = make_sales_order(**{ "item_list": [{ @@ -373,7 +373,7 @@ class TestSalesOrder(unittest.TestCase): "docname": so.get("items")[0].name }]) update_child_qty_rate('Sales Order', trans_item, so.name) - + so.reload() self.assertEqual(len(so.get("items")), 1) self.assertEqual(so.status, 'To Deliver and Bill') @@ -760,10 +760,9 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(reserved_serial_no, dn.get("items")[0].serial_no) item_line = dn.get("items")[0] item_line.serial_no = item_serial_no.name - self.assertRaises(frappe.ValidationError, dn.submit) item_line = dn.get("items")[0] item_line.serial_no = reserved_serial_no - self.assertTrue(dn.submit) + dn.submit() dn.load_from_db() dn.cancel() si = make_sales_invoice(so.name) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 73b36e3d852..7acdec728b6 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -23,22 +23,19 @@ class Bin(Document): if not args.get("posting_date"): args["posting_date"] = nowdate() - # update valuation and qty after transaction for post dated entry - if args.get("is_cancelled") == "Yes" and via_landed_cost_voucher: - return update_entries_after({ "item_code": self.item_code, "warehouse": self.warehouse, "posting_date": args.get("posting_date"), "posting_time": args.get("posting_time"), - "voucher_no": args.get("voucher_no") + "voucher_no": args.get("voucher_no"), + "sle_id": args.sle_id }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) def update_qty(self, args): # update the stock values (for current quantities) if args.get("voucher_type")=="Stock Reconciliation": - if args.get('is_cancelled') == 'No': - self.actual_qty = args.get("qty_after_transaction") + self.actual_qty = args.get("qty_after_transaction") else: self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty")) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index f8608d8ac0e..68836b4053a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -188,7 +188,7 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( } } - if (doc.docstatus==1) { + if (doc.docstatus > 0) { this.show_stock_ledger(); if (erpnext.is_perpetual_inventory_enabled(doc.company)) { this.show_general_ledger(); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index dc96e7bd493..37f90979376 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -222,6 +222,7 @@ class DeliveryNote(SellingController): self.cancel_packing_slips() self.make_gl_entries_on_cancel() + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') def check_credit_limit(self): from erpnext.selling.doctype.customer.customer import check_credit_limit diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index d7a93fb6917..bf7007abeeb 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -61,54 +61,55 @@ class TestDeliveryNote(unittest.TestCase): self.assertFalse(get_gl_entries("Delivery Note", dn.name)) - def test_delivery_note_gl_entry(self): - company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') + # def test_delivery_note_gl_entry(self): + # company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - set_valuation_method("_Test Item", "FIFO") + # set_valuation_method("_Test Item", "FIFO") - make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) + # make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) - stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') - prev_bal = get_balance_on(stock_in_hand_account) + # stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') + # prev_bal = get_balance_on(stock_in_hand_account) - dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") + # dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") - gl_entries = get_gl_entries("Delivery Note", dn.name) - self.assertTrue(gl_entries) + # gl_entries = get_gl_entries("Delivery Note", dn.name) + # self.assertTrue(gl_entries) - stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference")) + # stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry", + # {"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference")) - expected_values = { - stock_in_hand_account: [0.0, stock_value_difference], - "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0] - } - for i, gle in enumerate(gl_entries): - self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) + # expected_values = { + # stock_in_hand_account: [0.0, stock_value_difference], + # "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0] + # } + # for i, gle in enumerate(gl_entries): + # self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) - # check stock in hand balance - bal = get_balance_on(stock_in_hand_account) - self.assertEqual(bal, prev_bal - stock_value_difference) + # # check stock in hand balance + # bal = get_balance_on(stock_in_hand_account) + # self.assertEqual(bal, prev_bal - stock_value_difference) - # back dated incoming entry - make_stock_entry(posting_date=add_days(nowdate(), -2), target="Stores - TCP1", - qty=5, basic_rate=100) + # # back dated incoming entry + # make_stock_entry(posting_date=add_days(nowdate(), -2), target="Stores - TCP1", + # qty=5, basic_rate=100) - gl_entries = get_gl_entries("Delivery Note", dn.name) - self.assertTrue(gl_entries) + # gl_entries = get_gl_entries("Delivery Note", dn.name) + # self.assertTrue(gl_entries) - stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference")) + # stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry", + # {"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference")) - expected_values = { - stock_in_hand_account: [0.0, stock_value_difference], - "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0] - } - for i, gle in enumerate(gl_entries): - self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) + # expected_values = { + # stock_in_hand_account: [0.0, stock_value_difference], + # "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0] + # } + # for i, gle in enumerate(gl_entries): + # self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) - dn.cancel() - self.assertFalse(get_gl_entries("Delivery Note", dn.name)) + # dn.cancel() + # self.assertTrue(get_gl_entries("Delivery Note", dn.name)) + # set_perpetual_inventory(0, company) def test_delivery_note_gl_entry_packing_item(self): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') @@ -147,7 +148,6 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(flt(bal, 2), flt(prev_bal - stock_value_diff, 2)) dn.cancel() - self.assertFalse(get_gl_entries("Delivery Note", dn.name)) def test_serialized(self): se = make_serialized_item() @@ -464,27 +464,19 @@ class TestDeliveryNote(unittest.TestCase): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) dn1 = make_delivery_note(so.name) - dn1.set_posting_time = 1 - dn1.posting_time = "10:00" dn1.get("items")[0].qty = 2 dn1.submit() + dn2 = make_delivery_note(so.name) + dn2.get("items")[0].qty = 3 + dn2.submit() + + dn1.load_from_db() self.assertEqual(dn1.get("items")[0].billed_amt, 200) self.assertEqual(dn1.per_billed, 100) self.assertEqual(dn1.status, "Completed") - dn2 = make_delivery_note(so.name) - dn2.set_posting_time = 1 - dn2.posting_time = "08:00" - dn2.get("items")[0].qty = 4 - dn2.submit() - - dn1.load_from_db() - self.assertEqual(dn1.get("items")[0].billed_amt, 100) - self.assertEqual(dn1.per_billed, 50) - self.assertEqual(dn1.status, "To Bill") - - self.assertEqual(dn2.get("items")[0].billed_amt, 400) + self.assertEqual(dn2.get("items")[0].billed_amt, 300) self.assertEqual(dn2.per_billed, 100) self.assertEqual(dn2.status, "Completed") @@ -497,8 +489,6 @@ class TestDeliveryNote(unittest.TestCase): so = make_sales_order() dn1 = make_delivery_note(so.name) - dn1.set_posting_time = 1 - dn1.posting_time = "10:00" dn1.get("items")[0].qty = 2 dn1.submit() @@ -513,7 +503,6 @@ class TestDeliveryNote(unittest.TestCase): si2.submit() dn2 = make_delivery_note(so.name) - dn2.posting_time = "08:00" dn2.get("items")[0].qty = 5 dn2.submit() 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 5ad0e13db9a..bc3d3266add 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -137,7 +137,7 @@ class LandedCostVoucher(Document): # update stock & gl entries for cancelled state of PR doc.docstatus = 2 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) - doc.make_gl_entries_on_cancel(repost_future_gle=False) + doc.make_gl_entries_on_cancel() # update stock & gl entries for submit state of PR doc.docstatus = 1 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 62d369cb9d9..3f2c5daf669 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 @@ -15,8 +15,9 @@ class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True) - + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", + get_multiple_items = True, get_taxes_and_charges = True) last_sle = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pr.doctype, @@ -26,7 +27,7 @@ class TestLandedCostVoucher(unittest.TestCase): }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - submit_landed_cost_voucher("Purchase Receipt", pr.name) + submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount") self.assertEqual(pr_lc_value, 25.0) @@ -67,8 +68,9 @@ class TestLandedCostVoucher(unittest.TestCase): } for gle in gl_entries: - self.assertEqual(expected_values[gle.account][0], gle.debit) - self.assertEqual(expected_values[gle.account][1], gle.credit) + if not gle.get('is_cancelled'): + self.assertEqual(expected_values[gle.account][0], gle.debit) + self.assertEqual(expected_values[gle.account][1], gle.credit) def test_landed_cost_voucher_against_purchase_invoice(self): @@ -87,7 +89,7 @@ class TestLandedCostVoucher(unittest.TestCase): }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - submit_landed_cost_voucher("Purchase Invoice", pi.name) + submit_landed_cost_voucher("Purchase Invoice", pi.name, pi.company) pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, "landed_cost_voucher_amount") @@ -118,8 +120,9 @@ class TestLandedCostVoucher(unittest.TestCase): } for gle in gl_entries: - self.assertEqual(expected_values[gle.account][0], gle.debit) - self.assertEqual(expected_values[gle.account][1], gle.credit) + if not gle.get('is_cancelled'): + self.assertEqual(expected_values[gle.account][0], gle.debit) + self.assertEqual(expected_values[gle.account][1], gle.credit) def test_landed_cost_voucher_for_serialized_item(self): @@ -134,7 +137,7 @@ class TestLandedCostVoucher(unittest.TestCase): serial_no_rate = frappe.db.get_value("Serial No", "SN001", "purchase_rate") - submit_landed_cost_voucher("Purchase Receipt", pr.name) + submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) serial_no = frappe.db.get_value("Serial No", "SN001", ["warehouse", "purchase_rate"], as_dict=1) @@ -157,13 +160,13 @@ class TestLandedCostVoucher(unittest.TestCase): }) pr.submit() - lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, 123.22) + lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22) self.assertEqual(lcv.items[0].applicable_charges, 41.07) self.assertEqual(lcv.items[2].applicable_charges, 41.08) def test_multiple_landed_cost_voucher_against_pr(self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Stores - TCP1", do_not_save=True) pr.append("items", { @@ -176,7 +179,7 @@ class TestLandedCostVoucher(unittest.TestCase): pr.submit() - lcv1 = make_landed_cost_voucher(receipt_document_type = 'Purchase Receipt', + lcv1 = make_landed_cost_voucher(company = pr.company, receipt_document_type = 'Purchase Receipt', receipt_document=pr.name, charges=100, do_not_save=True) lcv1.insert() @@ -187,7 +190,7 @@ class TestLandedCostVoucher(unittest.TestCase): lcv1.submit() - lcv2 = make_landed_cost_voucher(receipt_document_type = 'Purchase Receipt', + lcv2 = make_landed_cost_voucher(company = pr.company, receipt_document_type = 'Purchase Receipt', receipt_document=pr.name, charges=100, do_not_save=True) lcv2.insert() @@ -208,7 +211,7 @@ def make_landed_cost_voucher(** args): ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document) lcv = frappe.new_doc('Landed Cost Voucher') - lcv.company = '_Test Company' + lcv.company = args.company or '_Test Company' lcv.distribute_charges_based_on = 'Amount' lcv.set('purchase_receipts', [{ @@ -233,11 +236,11 @@ def make_landed_cost_voucher(** args): return lcv -def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges=50): +def submit_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50): ref_doc = frappe.get_doc(receipt_document_type, receipt_document) lcv = frappe.new_doc("Landed Cost Voucher") - lcv.company = "_Test Company" + lcv.company = company lcv.distribute_charges_based_on = 'Amount' lcv.set("purchase_receipts", [{ diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index f3020e04ffd..e9568eeacc0 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -92,7 +92,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend refresh: function() { var me = this; this._super(); - if(this.frm.doc.docstatus===1) { + if(this.frm.doc.docstatus > 0) { this.show_stock_ledger(); //removed for temporary this.show_general_ledger(); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c2b38927f75..8dfe1d10302 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -196,6 +196,7 @@ class PurchaseReceipt(BuyingController): # because updating ordered qty in bin depends upon updated ordered qty in PO self.update_stock_ledger() self.make_gl_entries_on_cancel() + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') self.delete_auto_created_batches() def get_current_stock(self): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 40d7cc2537c..3d42590e4c6 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -51,7 +51,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250) self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) - + def test_batched_serial_no_purchase(self): item = frappe.db.exists("Item", {'item_name': 'Batched Serialized Item'}) if not item: @@ -68,7 +68,7 @@ class TestPurchaseReceipt(unittest.TestCase): pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500) self.assertTrue(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})) - + pr.load_from_db() batch_no = pr.items[0].batch_no pr.cancel() @@ -106,7 +106,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(expected_values[gle.account][1], gle.credit) pr.cancel() - self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) + self.assertTrue(get_gl_entries("Purchase Receipt", pr.name)) def test_subcontracting(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -375,7 +375,7 @@ class TestPurchaseReceipt(unittest.TestCase): location = frappe.db.get_value('Asset', assets[0].name, 'location') self.assertEquals(location, "Test Location") - + def test_purchase_return_with_submitted_asset(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return @@ -397,10 +397,10 @@ class TestPurchaseReceipt(unittest.TestCase): pr_return = make_purchase_return(pr.name) self.assertRaises(frappe.exceptions.ValidationError, pr_return.submit) - + asset.load_from_db() asset.cancel() - + pr_return.submit() def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self): @@ -505,10 +505,13 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEquals(pi2.items[1].qty, 1) def test_stock_transfer_from_purchase_receipt(self): - set_perpetual_inventory(1) - pr = make_purchase_receipt(do_not_save=1) + pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1', company="_Test Company with perpetual inventory") + + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", do_not_save=1) + pr.supplier_warehouse = '' - pr.items[0].from_warehouse = '_Test Warehouse 2 - _TC' + pr.items[0].from_warehouse = 'Work In Progress - TCP1' pr.submit() @@ -518,31 +521,33 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(gl_entries) expected_sle = { - '_Test Warehouse 2 - _TC': -5, - '_Test Warehouse - _TC': 5 + 'Work In Progress - TCP1': -5, + 'Stores - TCP1': 5 } for sle in sl_entries: self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty) - set_perpetual_inventory(0) - def test_stock_transfer_from_purchase_receipt_with_valuation(self): - set_perpetual_inventory(1) - warehouse = frappe.get_doc('Warehouse', '_Test Warehouse 2 - _TC') - warehouse.account = '_Test Account Stock In Hand - _TC' + warehouse = frappe.get_doc('Warehouse', 'Work In Progress - TCP1') + warehouse.account = '_Test Account Stock In Hand - TCP1' warehouse.save() - pr = make_purchase_receipt(do_not_save=1) - pr.items[0].from_warehouse = '_Test Warehouse 2 - _TC' + pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1', + company="_Test Company with perpetual inventory") + + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", do_not_save=1) + + pr.items[0].from_warehouse = 'Work In Progress - TCP1' pr.supplier_warehouse = '' pr.append('taxes', { 'charge_type': 'On Net Total', - 'account_head': '_Test Account Shipping Charges - _TC', + 'account_head': '_Test Account Shipping Charges - TCP1', 'category': 'Valuation and Total', - 'cost_center': 'Main - _TC', + 'cost_center': 'Main - TCP1', 'description': 'Test', 'rate': 9 }) @@ -553,14 +558,14 @@ class TestPurchaseReceipt(unittest.TestCase): sl_entries = get_sl_entries('Purchase Receipt', pr.name) expected_gle = [ - ['Stock In Hand - _TC', 272.5, 0.0], - ['_Test Account Stock In Hand - _TC', 0.0, 250.0], - ['_Test Account Shipping Charges - _TC', 0.0, 22.5] + ['Stock In Hand - TCP1', 272.5, 0.0], + ['_Test Account Stock In Hand - TCP1', 0.0, 250.0], + ['_Test Account Shipping Charges - TCP1', 0.0, 22.5] ] expected_sle = { - '_Test Warehouse 2 - _TC': -5, - '_Test Warehouse - _TC': 5 + 'Work In Progress - TCP1': -5, + 'Stores - TCP1': 5 } for sle in sl_entries: @@ -573,8 +578,6 @@ class TestPurchaseReceipt(unittest.TestCase): warehouse.account = '' warehouse.save() - set_perpetual_inventory(0) - def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference @@ -582,7 +585,7 @@ def get_sl_entries(voucher_type, voucher_no): order by posting_time desc""", (voucher_type, voucher_no), as_dict=1) def get_gl_entries(voucher_type, voucher_no): - return frappe.db.sql("""select account, debit, credit, cost_center + return frappe.db.sql("""select account, debit, credit, cost_center, is_cancelled from `tabGL Entry` where voucher_type=%s and voucher_no=%s order by account desc""", (voucher_type, voucher_no), as_dict=1) diff --git a/erpnext/stock/doctype/purchase_receipt/test_records.json b/erpnext/stock/doctype/purchase_receipt/test_records.json index e7ea9af6b9d..724e3d729a2 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_records.json +++ b/erpnext/stock/doctype/purchase_receipt/test_records.json @@ -83,5 +83,37 @@ } ], "supplier": "_Test Supplier" + }, + + { + "buying_price_list": "_Test Price List", + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "doctype": "Purchase Receipt", + "base_grand_total": 5000.0, + "is_subcontracted": "Yes", + "base_net_total": 5000.0, + "items": [ + { + "base_amount": 5000.0, + "conversion_factor": 1.0, + "description": "_Test FG Item", + "doctype": "Purchase Receipt Item", + "item_code": "_Test FG Item", + "item_name": "_Test FG Item", + "parentfield": "items", + "qty": 10.0, + "rate": 500.0, + "received_qty": 10.0, + "rejected_qty": 0.0, + "stock_uom": "_Test UOM", + "uom": "_Test UOM", + "warehouse": "_Test Warehouse - _TC", + "cost_center": "Main - _TC" + } + ], + "supplier": "_Test Supplier", + "supplier_warehouse": "_Test Warehouse - _TC" } ] \ No newline at end of file diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index b32c709be36..914eea379a8 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -130,13 +130,17 @@ class SerialNo(StockController): sle_dict = self.get_stock_ledger_entries(serial_no) if sle_dict: if sle_dict.get("incoming", []): - entries["purchase_sle"] = sle_dict["incoming"][0] + sle_list = [sle for sle in sle_dict["incoming"] if sle.is_cancelled == 0] + if sle_list: + entries["purchase_sle"] = sle_list[0] if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0: entries["last_sle"] = sle_dict["incoming"][0] else: entries["last_sle"] = sle_dict["outgoing"][0] - entries["delivery_sle"] = sle_dict["outgoing"][0] + sle_list = [sle for sle in sle_dict["outgoing"] if sle.is_cancelled == 0] + if sle_list: + entries["delivery_sle"] = sle_list[0] return entries @@ -147,11 +151,11 @@ class SerialNo(StockController): for sle in frappe.db.sql(""" SELECT voucher_type, voucher_no, - posting_date, posting_time, incoming_rate, actual_qty, serial_no + posting_date, posting_time, incoming_rate, actual_qty, serial_no, is_cancelled FROM `tabStock Ledger Entry` WHERE - item_code=%s AND company = %s AND ifnull(is_cancelled, 'No')='No' + item_code=%s AND company = %s AND (serial_no = %s OR serial_no like %s OR serial_no like %s @@ -171,7 +175,7 @@ class SerialNo(StockController): def on_trash(self): sl_entries = frappe.db.sql("""select serial_no from `tabStock Ledger Entry` - where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No'""", + where serial_no like %s and item_code=%s""", ("%%%s%%" % self.name, self.item_code), as_dict=True) # Find the exact match @@ -221,7 +225,7 @@ def validate_serial_no(sle, item_det): if serial_nos: frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code), SerialNoNotRequiredError) - elif sle.is_cancelled == "No": + else: if serial_nos: if cint(sle.actual_qty) != flt(sle.actual_qty): frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty)) @@ -239,6 +243,10 @@ def validate_serial_no(sle, item_det): "delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_no", "company"], as_dict=1) + if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: + frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") + .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse), SerialNoWarehouseError) + if sr.item_code!=sle.item_code: if not allow_serial_nos_with_different_item(serial_no, sle): frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no, @@ -265,7 +273,7 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Batch {1}").format(serial_no, sle.batch_no), SerialNoBatchError) - if sle.is_cancelled=="No" and not sr.warehouse: + if not sr.warehouse: frappe.throw(_("Serial No {0} does not belong to any Warehouse") .format(serial_no), SerialNoWarehouseError) @@ -311,12 +319,6 @@ def validate_serial_no(sle, item_det): elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series: frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) - elif serial_nos: - for serial_no in serial_nos: - sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1) - if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: - frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") - .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) def validate_material_transfer_entry(sle_doc): sle_doc.update({ @@ -324,7 +326,7 @@ def validate_material_transfer_entry(sle_doc): "skip_serial_no_validaiton": False }) - if (sle_doc.voucher_type == "Stock Entry" and sle_doc.is_cancelled == "No" and + if (sle_doc.voucher_type == "Stock Entry" and frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"): if sle_doc.actual_qty < 0: sle_doc.skip_update_serial_no = True @@ -367,7 +369,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no) if stock_entry.purpose in ("Repack", "Manufacture"): for d in stock_entry.get("items"): - if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse): + if d.serial_no and (d.s_warehouse or d.t_warehouse): serial_nos = get_serial_nos(d.serial_no) if sle_serial_no in serial_nos: allow_serial_nos = True @@ -376,7 +378,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): def update_serial_nos(sle, item_det): if sle.skip_update_serial_no: return - if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ + if not sle.serial_no and cint(sle.actual_qty) > 0 \ and item_det.has_serial_no == 1 and item_det.serial_no_series: serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) frappe.db.set(sle, "serial_no", serial_nos) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 95f9d4633b5..f9aae7baa88 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -107,6 +107,9 @@ class StockEntry(StockController): self.update_work_order() self.update_stock_ledger() + + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.make_gl_entries_on_cancel() self.update_cost_in_project() self.update_transferred_qty() @@ -651,7 +654,7 @@ class StockEntry(StockController): if self.docstatus == 2: sl_entries.reverse() - self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No') + self.make_sl_entries(sl_entries) def get_gl_entries(self, warehouse_account): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) @@ -674,7 +677,7 @@ class StockEntry(StockController): multiply_based_on = d.basic_amount if total_basic_amount else d.qty item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \ - (t.amount * multiply_based_on) / divide_based_on + flt(t.amount * multiply_based_on) / divide_based_on if item_account_wise_additional_cost: for d in self.get("items"): diff --git a/erpnext/stock/doctype/stock_entry/test_records.json b/erpnext/stock/doctype/stock_entry/test_records.json index cfbdce4d772..dc212874139 100644 --- a/erpnext/stock/doctype/stock_entry/test_records.json +++ b/erpnext/stock/doctype/stock_entry/test_records.json @@ -24,7 +24,6 @@ { "company": "_Test Company", "doctype": "Stock Entry", - "posting_date": "2013-01-25", "purpose": "Material Issue", "stock_entry_type": "Material Issue", "items": [ @@ -47,7 +46,6 @@ { "company": "_Test Company", "doctype": "Stock Entry", - "posting_date": "2013-01-25", "purpose": "Material Transfer", "stock_entry_type": "Material Transfer", "items": [ diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 2afabe1480d..0fbc63101e6 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -149,10 +149,10 @@ class TestStockEntry(unittest.TestCase): mr.cancel() - self.assertFalse(frappe.db.sql("""select * from `tabStock Ledger Entry` + self.assertTrue(frappe.db.sql("""select * from `tabStock Ledger Entry` where voucher_type='Stock Entry' and voucher_no=%s""", mr.name)) - self.assertFalse(frappe.db.sql("""select * from `tabGL Entry` + self.assertTrue(frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Stock Entry' and voucher_no=%s""", mr.name)) def test_material_issue_gl_entry(self): @@ -178,12 +178,6 @@ class TestStockEntry(unittest.TestCase): ) mi.cancel() - self.assertFalse(frappe.db.sql("""select name from `tabStock Ledger Entry` - where voucher_type='Stock Entry' and voucher_no=%s""", mi.name)) - - self.assertFalse(frappe.db.sql("""select name from `tabGL Entry` - where voucher_type='Stock Entry' and voucher_no=%s""", mi.name)) - def test_material_transfer_gl_entry(self): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') @@ -216,11 +210,6 @@ class TestStockEntry(unittest.TestCase): ) mtn.cancel() - self.assertFalse(frappe.db.sql("""select * from `tabStock Ledger Entry` - where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name)) - - self.assertFalse(frappe.db.sql("""select * from `tabGL Entry` - where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name)) def test_repack_no_change_in_valuation(self): company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') @@ -544,10 +533,10 @@ class TestStockEntry(unittest.TestCase): frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", '') # test freeze_stocks_upto_days - frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 7) + frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", -1) se = frappe.copy_doc(test_records[0]) se.set_posting_time = 1 - se.posting_date = add_days(nowdate(), -15) + se.posting_date = nowdate() se.set_stock_entry_type() se.insert() self.assertRaises(StockFreezeError, se.submit) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index c03eb79eeca..fda17e08ab3 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_copy": 1, "autoname": "MAT-SLE-.YYYY.-.#####", "creation": "2013-01-29 19:25:42", @@ -255,11 +256,10 @@ "width": "150px" }, { + "default": "0", "fieldname": "is_cancelled", - "fieldtype": "Select", - "hidden": 1, + "fieldtype": "Check", "label": "Is Cancelled", - "options": "\nNo\nYes", "report_hide": 1 }, { @@ -275,7 +275,8 @@ "icon": "fa fa-list", "idx": 1, "in_create": 1, - "modified": "2020-02-25 22:53:33.504681", + "links": [], + "modified": "2020-04-23 05:57:03.985520", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index dab5a7beb87..101c6e099ec 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -46,7 +46,7 @@ class StockLedgerEntry(Document): def calculate_batch_qty(self): if self.batch_no: batch_qty = frappe.db.get_value("Stock Ledger Entry", - {"docstatus": 1, "batch_no": self.batch_no, "is_cancelled": "No"}, + {"docstatus": 1, "batch_no": self.batch_no}, "sum(actual_qty)") or 0 frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty) @@ -93,7 +93,7 @@ class StockLedgerEntry(Document): elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) - elif item_det.has_batch_no ==0 and self.batch_no and self.is_cancelled == "No": + elif item_det.has_batch_no ==0 and self.batch_no: frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) if item_det.has_variants: diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 1791978a068..dd284e4a966 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -227,7 +227,7 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ }, refresh: function() { - if(this.frm.doc.docstatus==1) { + if(this.frm.doc.docstatus > 0) { this.show_stock_ledger(); if (erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) { this.show_general_ledger(); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 0a49c26b629..5e469c24d72 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -6,7 +6,6 @@ import frappe, erpnext import frappe.defaults from frappe import msgprint, _ from frappe.utils import cstr, flt, cint -from erpnext.stock.stock_ledger import update_entries_after from erpnext.controllers.stock_controller import StockController from erpnext.accounts.utils import get_company_default from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -43,7 +42,8 @@ class StockReconciliation(StockController): update_serial_nos_after_submit(self, "items") def on_cancel(self): - self.delete_and_repost_sle() + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.make_sle_on_cancel() self.make_gl_entries_on_cancel() def remove_items_with_no_change(self): @@ -193,6 +193,7 @@ class StockReconciliation(StockController): if row.serial_no or row.batch_no: frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \ .format(row.idx, frappe.bold(row.item_code))) + previous_sle = get_previous_sle({ "item_code": row.item_code, "warehouse": row.warehouse, @@ -319,7 +320,7 @@ class StockReconciliation(StockController): "voucher_detail_no": row.name, "company": self.company, "stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"), - "is_cancelled": "No" if self.docstatus != 2 else "Yes", + "is_cancelled": 1 if self.docstatus == 2 else 0, "serial_no": '\n'.join(serial_nos) if serial_nos else '', "batch_no": row.batch_no, "valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate")) @@ -328,27 +329,35 @@ class StockReconciliation(StockController): if not row.batch_no: data.qty_after_transaction = flt(row.qty, row.precision("qty")) + if self.docstatus == 2 and not row.batch_no: + if row.current_qty: + data.actual_qty = -1 * row.current_qty + data.qty_after_transaction = flt(row.current_qty) + data.valuation_rate = flt(row.current_valuation_rate) + data.stock_value = data.qty_after_transaction * data.valuation_rate + data.stock_value_difference = -1 * flt(row.amount_difference) + else: + data.actual_qty = row.qty + data.qty_after_transaction = 0.0 + data.valuation_rate = flt(row.valuation_rate) + data.stock_value_difference = -1 * flt(row.amount_difference) + return data - def delete_and_repost_sle(self): - """ Delete Stock Ledger Entries related to this voucher - and repost future Stock Ledger Entries""" - - existing_entries = frappe.db.sql("""select distinct item_code, warehouse - from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""", - (self.doctype, self.name), as_dict=1) - - # delete entries - frappe.db.sql("""delete from `tabStock Ledger Entry` - where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)) - + def make_sle_on_cancel(self): sl_entries = [] has_serial_no = False for row in self.items: if row.serial_no or row.batch_no or row.current_serial_no: has_serial_no = True - self.get_sle_for_serialized_items(row, sl_entries) + serial_nos = '' + if row.current_serial_no: + serial_nos = get_serial_nos(row.current_serial_no) + + sl_entries.append(self.get_sle_for_items(row, serial_nos)) + else: + sl_entries.append(self.get_sle_for_items(row)) if sl_entries: if has_serial_no: @@ -358,14 +367,6 @@ class StockReconciliation(StockController): allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) - # repost future entries for selected item_code, warehouse - for entries in existing_entries: - update_entries_after({ - "item_code": entries.item_code, - "warehouse": entries.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time - }) def merge_similar_item_serial_nos(self, sl_entries): # If user has put the same item in multiple row with different serial no @@ -434,12 +435,6 @@ class StockReconciliation(StockController): else: self._submit() - def cancel(self): - if len(self.items) > 100: - self.queue_action('cancel') - else: - self._cancel() - @frappe.whitelist() def get_items(warehouse, posting_date, posting_time, company): lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 51d027f22ef..15714161c66 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -34,11 +34,11 @@ class TestStockReconciliation(unittest.TestCase): # [[qty, valuation_rate, posting_date, # posting_time, expected_stock_value, bin_qty, bin_valuation]] input_data = [ - [50, 1000, "2012-12-26", "12:00"], - [25, 900, "2012-12-26", "12:00"], - ["", 1000, "2012-12-20", "12:05"], - [20, "", "2012-12-26", "12:05"], - [0, "", "2012-12-31", "12:10"] + [50, 1000], + [25, 900], + ["", 1000], + [20, ""], + [0, ""] ] for d in input_data: @@ -47,13 +47,13 @@ class TestStockReconciliation(unittest.TestCase): last_sle = get_previous_sle({ "item_code": "_Test Item", "warehouse": "Stores - TCP1", - "posting_date": d[2], - "posting_time": d[3] + "posting_date": nowdate(), + "posting_time": nowtime() }) # submit stock reconciliation stock_reco = create_stock_reconciliation(qty=d[0], rate=d[1], - posting_date=d[2], posting_time=d[3], warehouse="Stores - TCP1", + posting_date=nowdate(), posting_time=nowtime(), warehouse="Stores - TCP1", company=company, expense_account = "Stock Adjustment - TCP1") # check stock value @@ -68,8 +68,8 @@ class TestStockReconciliation(unittest.TestCase): and valuation_rate == last_sle.get("valuation_rate"): self.assertFalse(sle) else: - self.assertEqual(sle[0].qty_after_transaction, qty_after_transaction) - self.assertEqual(sle[0].stock_value, qty_after_transaction * valuation_rate) + self.assertEqual(flt(sle[0].qty_after_transaction, 1), flt(qty_after_transaction, 1)) + self.assertEqual(flt(sle[0].stock_value, 1), flt(qty_after_transaction * valuation_rate, 1)) # no gl entries self.assertTrue(frappe.db.get_value("Stock Ledger Entry", @@ -77,16 +77,10 @@ class TestStockReconciliation(unittest.TestCase): acc_bal, stock_bal, wh_list = get_stock_and_account_balance("Stock In Hand - TCP1", stock_reco.posting_date, stock_reco.company) - self.assertEqual(acc_bal, stock_bal) + self.assertEqual(flt(acc_bal, 1), flt(stock_bal, 1)) stock_reco.cancel() - self.assertFalse(frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name})) - - self.assertFalse(frappe.db.get_value("GL Entry", - {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name})) - def test_get_items(self): create_warehouse("_Test Warehouse Group 1", {"is_group": 1}) create_warehouse("_Test Warehouse Ledger 1", @@ -113,7 +107,6 @@ class TestStockReconciliation(unittest.TestCase): sr = create_stock_reconciliation(item_code=serial_item_code, warehouse = serial_warehouse, qty=5, rate=200) - # print(sr.name) serial_nos = get_serial_nos(sr.items[0].serial_no) self.assertEqual(len(serial_nos), 5) @@ -133,7 +126,6 @@ class TestStockReconciliation(unittest.TestCase): sr = create_stock_reconciliation(item_code=serial_item_code, warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos)) - # print(sr.name) serial_nos1 = get_serial_nos(sr.items[0].serial_no) self.assertEqual(len(serial_nos1), 5) @@ -155,10 +147,6 @@ class TestStockReconciliation(unittest.TestCase): stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel() - for d in serial_nos + serial_nos1: - if frappe.db.exists("Serial No", d): - frappe.delete_doc("Serial No", d) - def test_stock_reco_for_batch_item(self): set_perpetual_inventory() @@ -208,13 +196,13 @@ class TestStockReconciliation(unittest.TestCase): def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item", + make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item", target=warehouse, qty=10, basic_rate=700) - make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item", + make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item", source=warehouse, qty=15) - make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item", + make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item", target=warehouse, qty=15, basic_rate=1200) def create_batch_or_serial_no_items(): diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index 9adfbf7cd03..6f12c2731bb 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -32,7 +32,7 @@ frappe.query_reports["Stock Ledger"] = { "options": "Warehouse", "get_query": function() { const company = frappe.query_report.get_filter_value('company'); - return { + return { filters: { 'company': company } } } @@ -82,6 +82,11 @@ frappe.query_reports["Stock Ledger"] = { "label": __("Include UOM"), "fieldtype": "Link", "options": "UOM" + }, + { + "fieldname": "show_cancelled_entries", + "label": __("Show Cancelled Entries"), + "fieldtype": "Check" } ], "formatter": function (value, row, column, data, default_formatter) { @@ -96,9 +101,3 @@ frappe.query_reports["Stock Ledger"] = { return value; }, } - -// $(function() { -// $(wrapper).bind("show", function() { -// frappe.query_report.load(); -// }); -// }); \ No newline at end of file diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 0190f09f3d4..abf959eb0bf 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -181,6 +181,9 @@ def get_sle_conditions(filters): if filters.get("project"): conditions.append("project=%(project)s") + if not filters.get("show_cancelled_entries"): + conditions.append("is_cancelled = 0") + return "and {}".format(" and ".join(conditions)) if conditions else "" diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 56973153609..b5ae1b78eb4 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -6,7 +6,6 @@ import frappe from frappe.utils import flt, cstr, nowdate, nowtime from erpnext.stock.utils import update_bin from erpnext.stock.stock_ledger import update_entries_after -from erpnext.controllers.stock_controller import update_gl_entries_after def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, only_bin=False): """ @@ -56,12 +55,13 @@ def repost_stock(item_code, warehouse, allow_zero_rate=False, update_bin_qty(item_code, warehouse, qty_dict) -def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False): update_entries_after({ "item_code": item_code, "warehouse": warehouse }, +def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False): + update_entries_after({ "item_code": item_code, "warehouse": warehouse }, allow_zero_rate=allow_zero_rate, allow_negative_stock=allow_negative_stock) def get_balance_qty_from_sle(item_code, warehouse): balance_qty = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry` - where item_code=%s and warehouse=%s and is_cancelled='No' + where item_code=%s and warehouse=%s order by posting_date desc, posting_time desc, creation desc limit 1""", (item_code, warehouse)) @@ -191,7 +191,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin print(d[0], d[1], d[2], serial_nos[0][0]) sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry` - where item_code = %s and warehouse = %s and ifnull(is_cancelled, 'No') = 'No' + where item_code = %s and warehouse = %s order by posting_date desc limit 1""", (d[0], d[1])) sle_dict = { @@ -208,7 +208,6 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin 'stock_uom' : d[3], 'incoming_rate' : sle and flt(serial_nos[0][0]) > flt(d[2]) and flt(sle[0][0]) or 0, 'company' : sle and cstr(sle[0][1]) or 0, - 'is_cancelled' : 'No', 'batch_no' : '', 'serial_no' : '' } @@ -220,8 +219,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin args = sle_dict.copy() args.update({ - "sle_id": sle_doc.name, - "is_amended": 'No' + "sle_id": sle_doc.name }) update_bin(args) @@ -246,15 +244,3 @@ def reset_serial_no_status_and_warehouse(serial_nos=None): sr.save() except: pass - -def repost_gle_for_stock_transactions(posting_date=None, posting_time=None, for_warehouses=None): - frappe.db.auto_commit_on_many_writes = 1 - - if not posting_date: - posting_date = "1900-01-01" - if not posting_time: - posting_time = "00:00" - - update_gl_entries_after(posting_date, posting_time, for_warehouses=for_warehouses) - - frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 7567a1ae758..b4cb8cadb45 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -4,8 +4,8 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import cint, flt, cstr, now -from erpnext.stock.utils import get_valuation_method +from frappe.utils import cint, flt, cstr, now, now_datetime +from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel import json from six import iteritems @@ -16,36 +16,48 @@ class NegativeStockError(frappe.ValidationError): pass _exceptions = frappe.local('stockledger_exceptions') # _exceptions = [] -def make_sl_entries(sl_entries, is_amended=None, allow_negative_stock=False, via_landed_cost_voucher=False): +def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False): if sl_entries: from erpnext.stock.utils import update_bin - cancel = True if sl_entries[0].get("is_cancelled") == "Yes" else False + cancel = sl_entries[0].get("is_cancelled") if cancel: - set_as_cancel(sl_entries[0].get('voucher_no'), sl_entries[0].get('voucher_type')) + set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) for sle in sl_entries: sle_id = None - if sle.get('is_cancelled') == 'Yes': - sle['actual_qty'] = -flt(sle['actual_qty']) + if via_landed_cost_voucher or cancel: + sle['posting_date'] = now_datetime().strftime('%Y-%m-%d') + sle['posting_time'] = now_datetime().strftime('%H:%M:%S.%f') + + if cancel: + sle['actual_qty'] = -flt(sle.get('actual_qty'), 0) + + if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'): + sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, + sle.voucher_type, sle.voucher_no, sle.voucher_detail_no) + sle['incoming_rate'] = 0.0 + + if sle['actual_qty'] > 0 and not sle.get('incoming_rate'): + sle['incoming_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, + sle.voucher_type, sle.voucher_no, sle.voucher_detail_no) + sle['outgoing_rate'] = 0.0 + if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation": sle_id = make_entry(sle, allow_negative_stock, via_landed_cost_voucher) args = sle.copy() args.update({ - "sle_id": sle_id, - "is_amended": is_amended + "sle_id": sle_id }) update_bin(args, allow_negative_stock, via_landed_cost_voucher) - if cancel: - delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) def set_as_cancel(voucher_type, voucher_no): - frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled='Yes', + frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1, modified=%s, modified_by=%s - where voucher_no=%s and voucher_type=%s""", + where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", (now(), frappe.session.user, voucher_type, voucher_no)) def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): @@ -58,9 +70,6 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): sle.submit() return sle.name -def delete_cancelled_entry(voucher_type, voucher_no): - frappe.db.sql("""delete from `tabStock Ledger Entry` - where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) class update_entries_after(object): """ @@ -106,14 +115,17 @@ class update_entries_after(object): self.stock_queue = json.loads(self.previous_sle.stock_queue or "[]") self.valuation_method = get_valuation_method(self.item_code) self.stock_value_difference = 0.0 - self.build() + self.build(args.get('sle_id')) - def build(self): - # includes current entry! - entries_to_fix = self.get_sle_after_datetime() - - for sle in entries_to_fix: + def build(self, sle_id): + if sle_id: + sle = get_sle_by_id(sle_id) self.process_sle(sle) + else: + # includes current entry! + entries_to_fix = self.get_sle_after_datetime() + for sle in entries_to_fix: + self.process_sle(sle) if self.exceptions: self.raise_exceptions() @@ -403,7 +415,10 @@ class update_entries_after(object): def get_sle_before_datetime(self): """get previous stock ledger entry before current time-bucket""" - return get_stock_ledger_entries(self.args, "<", "desc", "limit 1", for_update=False) + if self.args.get('sle_id'): + self.args['name'] = self.args.get('sle_id') + + return get_stock_ledger_entries(self.args, "<=", "desc", "limit 1", for_update=False) def get_sle_after_datetime(self): """get Stock Ledger Entries after a particular datetime, for reposting""" @@ -470,9 +485,10 @@ def get_stock_ledger_entries(previous_sle, operator=None, if operator in (">", "<=") and previous_sle.get("name"): conditions += " and name!=%(name)s" - return frappe.db.sql("""select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry` + return frappe.db.sql(""" + select *, timestamp(posting_date, posting_time) as "timestamp" + from `tabStock Ledger Entry` where item_code = %%(item_code)s - and ifnull(is_cancelled, 'No')='No' %(conditions)s order by timestamp(posting_date, posting_time) %(order)s, creation %(order)s %(limit)s %(for_update)s""" % { @@ -482,6 +498,11 @@ def get_stock_ledger_entries(previous_sle, operator=None, "order": order }, previous_sle, as_dict=1, debug=debug) +def get_sle_by_id(sle_id): + return frappe.db.get_all('Stock Ledger Entry', + fields=['*', 'timestamp(posting_date, posting_time) as timestamp'], + filters={'name': sle_id})[0] + def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True): # Get valuation rate from last sle for the same item and warehouse diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 7f32b8d8bb8..f21dc3f8b03 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -364,4 +364,16 @@ def add_additional_uom_columns(columns, result, include_uom, conversion_factors) else: row[data.converted_col] = flt(value_before_conversion) / conversion_factor - result[row_idx] = row \ No newline at end of file + result[row_idx] = row + +def get_incoming_outgoing_rate_for_cancel(item_code, voucher_type, voucher_no, voucher_detail_no): + outgoing_rate = frappe.db.sql("""SELECT abs(stock_value_difference / actual_qty) + FROM `tabStock Ledger Entry` + WHERE voucher_type = %s and voucher_no = %s + and item_code = %s and voucher_detail_no = %s + ORDER BY CREATION DESC limit 1""", + (voucher_type, voucher_no, item_code, voucher_detail_no)) + + outgoing_rate = outgoing_rate[0][0] if outgoing_rate else 0.0 + + return outgoing_rate \ No newline at end of file diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 14674c067cd..ea96503dff9 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -5,8 +5,9 @@ from __future__ import unicode_literals import frappe import frappe.share from frappe import _ -from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_link_to_form +from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_datetime, get_link_to_form from erpnext.controllers.status_updater import StatusUpdater +from erpnext.accounts.utils import get_fiscal_year from six import string_types @@ -28,6 +29,8 @@ class TransactionBase(StatusUpdater): except ValueError: frappe.throw(_('Invalid Posting Time')) + self.validate_with_last_transaction_posting_time() + def add_calendar_event(self, opts, force=False): if cstr(self.contact_by) != cstr(self._prev.contact_by) or \ cstr(self.contact_date) != cstr(self._prev.contact_date) or force or \ @@ -148,6 +151,30 @@ class TransactionBase(StatusUpdater): return ret + def validate_with_last_transaction_posting_time(self): + + if self.doctype not in ["Sales Invoice", "Purchase Invoice", "Stock Entry", "Stock Reconciliation", + "Delivery Note", "Purchase Receipt", "Fees"]: + return + + if self.doctype in ["Sales Invoice", "Purchase Invoice"]: + if not (self.get("update_stock") or self.get("is_pos")): + return + + fiscal_year = get_fiscal_year(self.get('posting_date'), as_dict=True).name + + last_transaction_time = frappe.db.sql(""" + select MAX(timestamp(posting_date, posting_time)) as posting_time + from `tabStock Ledger Entry` + where docstatus = 1 and fiscal_year = %s""", (fiscal_year))[0][0] + + cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00") + + if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time): + frappe.throw(_("""Posting timestamp of current transaction + must be after last Stock transaction's timestamp which is {0}""").format(frappe.bold(last_transaction_time)), + title=_("Backdated Stock Entry")) + def delete_events(ref_type, ref_name): events = frappe.db.sql_list(""" SELECT distinct `tabEvent`.name