Merge branch 'develop' into common-party-acc
diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py
index cd8e204..2ba2139 100644
--- a/erpnext/accounts/doctype/finance_book/test_finance_book.py
+++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py
@@ -9,19 +9,8 @@
import unittest
class TestFinanceBook(unittest.TestCase):
- def create_finance_book(self):
- if not frappe.db.exists("Finance Book", "_Test Finance Book"):
- finance_book = frappe.get_doc({
- "doctype": "Finance Book",
- "finance_book_name": "_Test Finance Book"
- }).insert()
- else:
- finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
-
- return finance_book
-
def test_finance_book(self):
- finance_book = self.create_finance_book()
+ finance_book = create_finance_book()
# create jv entry
jv = make_journal_entry("_Test Bank - _TC",
@@ -41,3 +30,14 @@
for gl_entry in gl_entries:
self.assertEqual(gl_entry.finance_book, finance_book.name)
+
+def create_finance_book():
+ if not frappe.db.exists("Finance Book", "_Test Finance Book"):
+ finance_book = frappe.get_doc({
+ "doctype": "Finance Book",
+ "finance_book_name": "_Test Finance Book"
+ }).insert()
+ else:
+ finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
+
+ return finance_book
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 439b1ed..d96bc27 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -533,8 +533,8 @@
source_exchange_rate: function(frm) {
if (frm.doc.paid_amount) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
- if(!frm.set_paid_amount_based_on_received_amount &&
- (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) {
+ // target exchange rate should always be same as source if both account currencies is same
+ if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index d2dffde..abacee9 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -55,14 +55,17 @@
self.validate_mandatory()
self.validate_reference_documents()
self.set_tax_withholding()
- self.apply_taxes()
self.set_amounts()
+ self.validate_amounts()
+ self.apply_taxes()
+ self.set_amounts_after_tax()
self.clear_unallocated_reference_document_rows()
self.validate_payment_against_negative_invoice()
self.validate_transaction_reference()
self.set_title()
self.set_remarks()
self.validate_duplicate_entry()
+ self.validate_payment_type_with_outstanding()
self.validate_allocated_amount()
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked()
@@ -118,6 +121,11 @@
if not self.get(field):
self.set(field, bank_data.account)
+ def validate_payment_type_with_outstanding(self):
+ total_outstanding = sum(d.allocated_amount for d in self.get('references'))
+ if total_outstanding < 0 and self.party_type == 'Customer' and self.payment_type == 'Receive':
+ frappe.throw(_("Cannot receive from customer against negative outstanding"), title=_("Incorrect Payment Type"))
+
def validate_allocated_amount(self):
for d in self.get("references"):
if (flt(d.allocated_amount))> 0:
@@ -236,7 +244,9 @@
self.company_currency, self.posting_date)
def set_target_exchange_rate(self, ref_doc=None):
- if self.paid_to and not self.target_exchange_rate:
+ if self.paid_from_account_currency == self.paid_to_account_currency:
+ self.target_exchange_rate = self.source_exchange_rate
+ elif self.paid_to and not self.target_exchange_rate:
if ref_doc:
if self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate")
@@ -468,13 +478,22 @@
def set_amounts(self):
self.set_received_amount()
self.set_amounts_in_company_currency()
- self.set_amounts_after_tax()
self.set_total_allocated_amount()
self.set_unallocated_amount()
self.set_difference_amount()
+ def validate_amounts(self):
+ self.validate_received_amount()
+
+ def validate_received_amount(self):
+ if self.paid_from_account_currency == self.paid_to_account_currency:
+ if self.paid_amount != self.received_amount:
+ frappe.throw(_("Received Amount cannot be greater than Paid Amount"))
+
def set_received_amount(self):
self.base_received_amount = self.base_paid_amount
+ if self.paid_from_account_currency == self.paid_to_account_currency:
+ self.received_amount = self.paid_amount
def set_amounts_after_tax(self):
applicable_tax = 0
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 801dadc..dac927b 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -107,7 +107,7 @@
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
- pe.target_exchange_rate = 50
+ pe.source_exchange_rate = 50
pe.insert()
pe.submit()
@@ -154,7 +154,7 @@
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
- pe.target_exchange_rate = 50
+ pe.source_exchange_rate = 50
pe.insert()
pe.submit()
@@ -491,7 +491,7 @@
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
- pe.target_exchange_rate = 55
+ pe.source_exchange_rate = 55
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",
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 a6e3bd9..289278e 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -50,9 +50,13 @@
.format(pce[0][0], self.posting_date))
def make_gl_entries(self):
+ gl_entries = self.get_gl_entries()
+ if gl_entries:
+ from erpnext.accounts.general_ledger import make_gl_entries
+ make_gl_entries(gl_entries)
+
+ def get_gl_entries(self):
gl_entries = []
- net_pl_balance = 0
-
pl_accounts = self.get_pl_balances()
for acc in pl_accounts:
@@ -60,6 +64,7 @@
gl_entries.append(self.get_gl_dict({
"account": acc.account,
"cost_center": acc.cost_center,
+ "finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
@@ -67,35 +72,13 @@
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
}, item=acc))
- net_pl_balance += flt(acc.bal_in_company_currency)
+ if gl_entries:
+ gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts)
+ gl_entries += gle_for_net_pl_bal
- if net_pl_balance:
- if self.cost_center_wise_pnl:
- costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts)
- gl_entries += costcenter_wise_gl_entries
- else:
- gl_entry = self.get_pnl_gl_entry(net_pl_balance)
- gl_entries.append(gl_entry)
-
- from erpnext.accounts.general_ledger import make_gl_entries
- make_gl_entries(gl_entries)
-
- def get_pnl_gl_entry(self, net_pl_balance):
- cost_center = frappe.db.get_value("Company", self.company, "cost_center")
- gl_entry = self.get_gl_dict({
- "account": self.closing_account_head,
- "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
- "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
- "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
- "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
- "cost_center": cost_center
- })
-
- self.update_default_dimensions(gl_entry)
-
- return gl_entry
-
- def get_costcenter_wise_pnl_gl_entries(self, pl_accounts):
+ return gl_entries
+
+ def get_pnl_gl_entry(self, pl_accounts):
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries = []
@@ -104,6 +87,7 @@
gl_entry = self.get_gl_dict({
"account": self.closing_account_head,
"cost_center": acc.cost_center or company_cost_center,
+ "finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
@@ -130,7 +114,7 @@
def get_pl_balances(self):
"""Get balance for dimension-wise pl accounts"""
- dimension_fields = ['t1.cost_center']
+ dimension_fields = ['t1.cost_center', 't1.finance_book']
self.accounting_dimensions = get_accounting_dimensions()
for dimension in self.accounting_dimensions:
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index f17a5c5..2d19391 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -8,6 +8,7 @@
from frappe.utils import flt, today
from erpnext.accounts.utils import get_fiscal_year, now
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
+from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestPeriodClosingVoucher(unittest.TestCase):
@@ -118,6 +119,58 @@
self.assertTrue(pcv_gle, expected_gle)
+ def test_period_closing_with_finance_book_entries(self):
+ frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
+
+ company = create_company()
+ surplus_account = create_account()
+ cost_center = create_cost_center("Test Cost Center 1")
+
+ create_sales_invoice(
+ company=company,
+ income_account="Sales - TPC",
+ expense_account="Cost of Goods Sold - TPC",
+ cost_center=cost_center,
+ rate=400,
+ debit_to="Debtors - TPC"
+ )
+ jv = make_journal_entry(
+ account1="Cash - TPC",
+ account2="Sales - TPC",
+ amount=400,
+ cost_center=cost_center,
+ posting_date=now()
+ )
+ jv.company = company
+ jv.finance_book = create_finance_book().name
+ jv.save()
+ jv.submit()
+
+ pcv = frappe.get_doc({
+ "transaction_date": today(),
+ "posting_date": today(),
+ "fiscal_year": get_fiscal_year(today())[0],
+ "company": company,
+ "closing_account_head": surplus_account,
+ "remarks": "Test",
+ "doctype": "Period Closing Voucher"
+ })
+ pcv.insert()
+ pcv.submit()
+
+ expected_gle = (
+ (surplus_account, 0.0, 400.0, ''),
+ (surplus_account, 0.0, 400.0, jv.finance_book),
+ ('Sales - TPC', 400.0, 0.0, ''),
+ ('Sales - TPC', 400.0, 0.0, jv.finance_book)
+ )
+
+ pcv_gle = frappe.db.sql("""
+ select account, debit, credit, finance_book from `tabGL Entry` where voucher_no=%s
+ """, (pcv.name))
+
+ self.assertTrue(pcv_gle, expected_gle)
+
def make_period_closing_voucher(self):
pcv = frappe.get_doc({
"doctype": "Period Closing Voucher",
diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
index b596c0c..5b18ebb 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
@@ -85,9 +85,15 @@
pcv_doc.load_from_db()
pcv_doc.cancel()
- si_doc.load_from_db()
+
+ cancelled_invoice = frappe.db.get_value(
+ 'POS Invoice Merge Log', {'pos_closing_entry': pcv_doc.name},
+ 'consolidated_invoice'
+ )
+ docstatus = frappe.db.get_value("Sales Invoice", cancelled_invoice, 'docstatus')
+ self.assertEqual(docstatus, 2)
+
pos_inv1.load_from_db()
- self.assertEqual(si_doc.docstatus, 2)
self.assertEqual(pos_inv1.status, 'Paid')
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 181e9f8..15c2922 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -16,7 +16,7 @@
onload(doc) {
super.onload();
- this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
this.frm.refresh_fields();
@@ -111,16 +111,12 @@
}
write_off_outstanding_amount_automatically() {
- if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
+ if (cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
// this will make outstanding amount 0
this.frm.set_value("write_off_amount",
flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
);
- this.frm.toggle_enable("write_off_amount", false);
-
- } else {
- this.frm.toggle_enable("write_off_amount", true);
}
this.calculate_outstanding_amount(false);
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index fcccb39..b819537 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -99,6 +99,7 @@
"loyalty_redemption_account",
"loyalty_redemption_cost_center",
"section_break_49",
+ "coupon_code",
"apply_discount_on",
"base_discount_amount",
"column_break_51",
@@ -1183,7 +1184,8 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "read_only_depends_on": "eval: doc.write_off_outstanding_amount_automatically"
},
{
"fieldname": "base_write_off_amount",
@@ -1549,12 +1551,20 @@
"no_copy": 1,
"options": "Sales Invoice",
"read_only": 1
+ },
+ {
+ "depends_on": "coupon_code",
+ "fieldname": "coupon_code",
+ "fieldtype": "Link",
+ "label": "Coupon Code",
+ "options": "Coupon Code",
+ "print_hide": 1
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2021-08-17 20:13:44.255437",
+ "modified": "2021-08-18 16:13:52.080543",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 8ec4ef2..034a217 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -44,6 +44,9 @@
self.validate_pos()
self.validate_payment_amount()
self.validate_loyalty_transaction()
+ if self.coupon_code:
+ from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
+ validate_coupon_code(self.coupon_code)
def on_submit(self):
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
@@ -58,6 +61,10 @@
self.check_phone_payments()
self.set_status(update=True)
+ if self.coupon_code:
+ from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
+ update_coupon_code_count(self.coupon_code,'used')
+
def before_cancel(self):
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
pos_closing_entry = frappe.get_all(
@@ -84,6 +91,10 @@
against_psi_doc.delete_loyalty_point_entry()
against_psi_doc.make_loyalty_point_entry()
+ if self.coupon_code:
+ from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
+ update_coupon_code_count(self.coupon_code,'cancelled')
+
def check_phone_payments(self):
for pay in self.payments:
if pay.type == "Phone" and pay.amount >= 0:
@@ -127,7 +138,7 @@
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
def validate_stock_availablility(self):
- if self.is_return:
+ if self.is_return or self.docstatus != 1:
return
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 556f49d..4903c50 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -198,12 +198,19 @@
set_serial_nos_based_on_fifo = frappe.db.get_single_value("Stock Settings",
"automatically_set_serial_nos_based_on_fifo")
+ item_code_list = tuple(item.get('item_code') for item in item_list)
+ query_items = frappe.get_all('Item', fields=['item_code','has_serial_no'], filters=[['item_code','in',item_code_list]],as_list=1)
+ serialized_items = dict()
+ for item_code, val in query_items:
+ serialized_items.setdefault(item_code, val)
+
for item in item_list:
args_copy = copy.deepcopy(args)
args_copy.update(item)
data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc)
out.append(data)
- if not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
+
+ if serialized_items.get(item.get('item_code')) and not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
out[0].update(get_serial_no_for_item(args_copy))
return out
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 8d65101..2071827 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -324,16 +324,12 @@
}
write_off_outstanding_amount_automatically() {
- if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
+ if (cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
// this will make outstanding amount 0
this.frm.set_value("write_off_amount",
flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
);
- this.frm.toggle_enable("write_off_amount", false);
-
- } else {
- this.frm.toggle_enable("write_off_amount", true);
}
this.calculate_outstanding_amount(false);
@@ -787,8 +783,6 @@
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();
},
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index e317443..5023c9c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1444,7 +1444,8 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "read_only_depends_on": "eval:doc.write_off_outstanding_amount_automatically"
},
{
"fieldname": "base_write_off_amount",
@@ -2014,7 +2015,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-08-17 20:16:12.737743",
+ "modified": "2021-08-18 16:07:45.122570",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c454a86..b48dc92 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -26,6 +26,7 @@
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
from erpnext.stock.utils import get_incoming_rate
+from erpnext.accounts.utils import PaymentEntryUnlinkError
class TestSalesInvoice(unittest.TestCase):
def make(self):
@@ -136,7 +137,7 @@
pe.paid_to_account_currency = si.currency
pe.source_exchange_rate = 1
pe.target_exchange_rate = 1
- pe.paid_amount = si.grand_total
+ pe.paid_amount = si.outstanding_amount
pe.insert()
pe.submit()
@@ -145,6 +146,42 @@
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
+ def test_payment_entry_unlink_against_standalone_credit_note(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+ si1 = create_sales_invoice(rate=1000)
+ si2 = create_sales_invoice(rate=300)
+ si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
+
+
+ pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC")
+ pe.append('references', {
+ 'reference_doctype': 'Sales Invoice',
+ 'reference_name': si2.name,
+ 'total_amount': si2.grand_total,
+ 'outstanding_amount': si2.outstanding_amount,
+ 'allocated_amount': si2.outstanding_amount
+ })
+
+ pe.append('references', {
+ 'reference_doctype': 'Sales Invoice',
+ 'reference_name': si3.name,
+ 'total_amount': si3.grand_total,
+ 'outstanding_amount': si3.outstanding_amount,
+ 'allocated_amount': si3.outstanding_amount
+ })
+
+ pe.reference_no = 'Test001'
+ pe.reference_date = nowdate()
+ pe.save()
+ pe.submit()
+
+ si2.load_from_db()
+ si2.cancel()
+
+ si1.load_from_db()
+ self.assertRaises(PaymentEntryUnlinkError, si1.cancel)
+
+
def test_sales_invoice_calculation_export_currency(self):
si = frappe.copy_doc(test_records[2])
si.currency = "USD"
@@ -2014,7 +2051,7 @@
data = get_ewb_data("Sales Invoice", [si.name])
- self.assertEqual(data['version'], '1.0.1118')
+ self.assertEqual(data['version'], '1.0.0421')
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')
self.assertEqual(data['billLists'][0]['fromTrdName'], '_Test Company')
self.assertEqual(data['billLists'][0]['toTrdName'], '_Test Customer')
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 4c7c567..3126138 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -101,7 +101,7 @@
def check_if_in_list(gle, gl_map, dimensions=None):
account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher',
- 'cost_center', 'against_voucher_type', 'party_type', 'project']
+ 'cost_center', 'against_voucher_type', 'party_type', 'project', 'finance_book']
if dimensions:
account_head_fieldnames = account_head_fieldnames + dimensions
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index b54646f..cedfc0f 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -535,6 +535,8 @@
if getdate(entry_date) > getdate(self.filters.report_date):
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
+ row.total_due = row.range1 + row.range2 + row.range3 + row.range4 + row.range5
+
def get_ageing_data(self, entry_date, row):
# [0-30, 30-60, 60-90, 90-120, 120-above]
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index e94b309..4bfb022 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -82,6 +82,7 @@
"range3": 0.0,
"range4": 0.0,
"range5": 0.0,
+ "total_due": 0.0,
"sales_person": []
}))
@@ -135,3 +136,6 @@
"{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
self.add_column(label=label, fieldname='range' + str(i+1))
+
+ # Add column for total due amount
+ self.add_column(label="Total Amount Due", fieldname='total_due')
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 56a67bb..fc42127 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -210,10 +210,10 @@
company_currency = get_company_currency(filters)
if filters.filter_based_on == 'Fiscal Year':
- start_date = fiscal_year.year_start_date
+ start_date = fiscal_year.year_start_date if filters.report != 'Balance Sheet' else None
end_date = fiscal_year.year_end_date
else:
- start_date = filters.period_start_date
+ start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
end_date = filters.period_end_date
gl_entries_by_account = {}
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index eca1e26..118f628 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -19,6 +19,7 @@
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
class FiscalYearError(frappe.ValidationError): pass
+class PaymentEntryUnlinkError(frappe.ValidationError): pass
@frappe.whitelist()
def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False):
@@ -555,10 +556,16 @@
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
for pe in linked_pe:
- pe_doc = frappe.get_doc("Payment Entry", pe)
- pe_doc.set_total_allocated_amount()
- pe_doc.set_unallocated_amount()
- pe_doc.clear_unallocated_reference_document_rows()
+ try:
+ pe_doc = frappe.get_doc("Payment Entry", pe)
+ pe_doc.set_amounts()
+ pe_doc.clear_unallocated_reference_document_rows()
+ pe_doc.validate_payment_type_with_outstanding()
+ except Exception as e:
+ msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
+ msg += '<br>'
+ msg += _("Please cancel payment entry manually first")
+ frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 61582b1..dfe26fa 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1896,6 +1896,11 @@
for d in data:
new_child_flag = False
+
+ if not d.get("item_code"):
+ # ignore empty rows
+ continue
+
if not d.get("docname"):
new_child_flag = True
check_doc_permissions(parent, 'create')
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 80ccc6d..5ee1f2f 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -329,7 +329,6 @@
target_doc.po_detail = source_doc.po_detail
target_doc.pr_detail = source_doc.pr_detail
target_doc.purchase_invoice_item = source_doc.name
- target_doc.price_list_rate = 0
elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
@@ -360,7 +359,6 @@
else:
target_doc.pos_invoice_item = source_doc.name
- target_doc.price_list_rate = 0
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 05edb25..7c6d355 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -595,7 +595,8 @@
self.doc.precision("outstanding_amount"))
if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
- self.update_paid_amount_for_return(total_amount_to_pay)
+ self.set_total_amount_to_default_mop(total_amount_to_pay)
+ self.calculate_paid_amount()
def calculate_paid_amount(self):
@@ -675,7 +676,7 @@
def set_item_wise_tax_breakup(self):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
- def update_paid_amount_for_return(self, total_amount_to_pay):
+ def set_total_amount_to_default_mop(self, total_amount_to_pay):
default_mode_of_payment = frappe.db.get_value('POS Payment Method',
{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
@@ -685,9 +686,7 @@
'mode_of_payment': default_mode_of_payment.mode_of_payment,
'amount': total_amount_to_pay,
'default': 1
- })
-
- self.calculate_paid_amount()
+ })
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js
index e9e6f81..dbf7514 100644
--- a/erpnext/payroll/doctype/salary_component/salary_component.js
+++ b/erpnext/payroll/doctype/salary_component/salary_component.js
@@ -4,18 +4,11 @@
frappe.ui.form.on('Salary Component', {
setup: function(frm) {
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
- let d = frappe.get_doc(cdt, cdn);
-
- let root_type = "Liability";
- if (frm.doc.type == "Deduction") {
- root_type = "Expense";
- }
-
+ var d = locals[cdt][cdn];
return {
filters: {
"is_group": 0,
- "company": d.company,
- "root_type": root_type
+ "company": d.company
}
};
});
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index e8f3122..702064f 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -754,8 +754,6 @@
}
});
this.frm.refresh_fields();
-
- this.calculate_paid_amount();
}
set_default_payment(total_amount_to_pay, update_paid_amount) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 9375e35..2538852 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -2242,12 +2242,19 @@
coupon_code() {
var me = this;
- frappe.run_serially([
- () => this.frm.doc.ignore_pricing_rule=1,
- () => me.ignore_pricing_rule(),
- () => this.frm.doc.ignore_pricing_rule=0,
- () => me.apply_pricing_rule()
- ]);
+ if (this.frm.doc.coupon_code) {
+ frappe.run_serially([
+ () => this.frm.doc.ignore_pricing_rule=1,
+ () => me.ignore_pricing_rule(),
+ () => this.frm.doc.ignore_pricing_rule=0,
+ () => me.apply_pricing_rule()
+ ]);
+ } else {
+ frappe.run_serially([
+ () => this.frm.doc.ignore_pricing_rule=1,
+ () => me.ignore_pricing_rule()
+ ]);
+ }
}
};
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index f1b9235..f240b8c 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -563,7 +563,7 @@
},
],
primary_action: function() {
- const trans_items = this.get_values()["trans_items"];
+ const trans_items = this.get_values()["trans_items"].filter((item) => !!item.item_code);
frappe.call({
method: 'erpnext.controllers.accounts_controller.update_child_qty_rate',
freeze: true,
diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss
index c77b2ce..1677e9b 100644
--- a/erpnext/public/scss/point-of-sale.scss
+++ b/erpnext/public/scss/point-of-sale.scss
@@ -860,6 +860,8 @@
.invoice-fields {
overflow-y: scroll;
+ height: 100%;
+ padding-right: var(--padding-sm);
}
}
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index a6ab6ab..4db5551 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -531,6 +531,7 @@
fieldtype='Link', options='Salary Component', insert_after='hra_section'),
dict(fieldname='hra_component', label='HRA Component',
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
+ dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
dict(fieldname='arrear_component', label='Arrear Component',
fieldtype='Link', options='Salary Component', insert_after='hra_component'),
dict(fieldname='non_profit_section', label='Non Profit Settings',
@@ -539,6 +540,7 @@
fieldtype='Data', insert_after='non_profit_section'),
dict(fieldname='with_effect_from', label='80G With Effect From',
fieldtype='Date', insert_after='company_80g_number'),
+ dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
dict(fieldname='pan_details', label='PAN Number',
fieldtype='Data', insert_after='with_effect_from')
],
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 4e4dcf8..ce5aa10 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -475,7 +475,7 @@
ewaybills.append(data)
data = {
- 'version': '1.0.1118',
+ 'version': '1.0.0421',
'billLists': ewaybills
}
diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py
index ba1aeaf..56f609e 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -6,9 +6,8 @@
from frappe.utils import flt, cstr
from erpnext.controllers.taxes_and_totals import get_itemised_tax
from frappe import _
-from frappe.core.doctype.file.file import remove_file
+from frappe.utils.file_manager import remove_file
from six import string_types
-from frappe.desk.form.load import get_attachments
from erpnext.regional.italy import state_codes
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 22bf3fc..2de57c8 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -394,6 +394,10 @@
}
_set_batch_number(doc) {
+ if (doc.batch_no) {
+ return
+ }
+
let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)};
if (doc.has_serial_no && doc.serial_no) {
args['serial_no'] = doc.serial_no
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 8f83d3c..56700af 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -46,6 +46,43 @@
});
},
+ change_abbreviation(frm) {
+ var dialog = new frappe.ui.Dialog({
+ title: "Replace Abbr",
+ fields: [
+ {"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr",
+ "reqd": 1 },
+ {"fieldtype": "Button", "label": "Update", "fieldname": "update"},
+ ]
+ });
+
+ dialog.fields_dict.update.$input.click(function() {
+ var args = dialog.get_values();
+ if (!args) return;
+ frappe.show_alert(__("Update in progress. It might take a while."));
+ return frappe.call({
+ method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr",
+ args: {
+ "company": frm.doc.name,
+ "old": frm.doc.abbr,
+ "new": args.new_abbr
+ },
+ callback: function(r) {
+ if (r.exc) {
+ frappe.msgprint(__("There were errors."));
+ return;
+ } else {
+ frm.set_value("abbr", args.new_abbr);
+ }
+ dialog.hide();
+ frm.refresh();
+ },
+ btn: this
+ });
+ });
+ dialog.show();
+ },
+
company_name: function(frm) {
if(frm.doc.__islocal) {
// add missing " " arg in split method
@@ -127,6 +164,10 @@
}, __('Manage'));
}
}
+
+ frm.add_custom_button(__('Change Abbreviation'), () => {
+ frm.trigger('change_abbreviation');
+ }, __('Manage'));
}
erpnext.company.set_chart_of_accounts_options(frm.doc);
@@ -204,43 +245,6 @@
}
}
-cur_frm.cscript.change_abbr = function() {
- var dialog = new frappe.ui.Dialog({
- title: "Replace Abbr",
- fields: [
- {"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr",
- "reqd": 1 },
- {"fieldtype": "Button", "label": "Update", "fieldname": "update"},
- ]
- });
-
- dialog.fields_dict.update.$input.click(function() {
- var args = dialog.get_values();
- if(!args) return;
- frappe.show_alert(__("Update in progress. It might take a while."));
- return frappe.call({
- method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr",
- args: {
- "company": cur_frm.doc.name,
- "old": cur_frm.doc.abbr,
- "new": args.new_abbr
- },
- callback: function(r) {
- if(r.exc) {
- frappe.msgprint(__("There were errors."));
- return;
- } else {
- cur_frm.set_value("abbr", args.new_abbr);
- }
- dialog.hide();
- cur_frm.refresh();
- },
- btn: this
- })
- });
- dialog.show();
-}
-
erpnext.company.setup_queries = function(frm) {
$.each([
["default_bank_account", {"account_type": "Bank"}],
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index e6ec496..e4ee3ec 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -12,33 +12,48 @@
"details",
"company_name",
"abbr",
- "change_abbr",
+ "default_currency",
+ "country",
"is_group",
"cb0",
- "domain",
- "parent_company",
- "charts_section",
- "default_currency",
"default_letter_head",
- "default_holiday_list",
- "default_finance_book",
- "default_selling_terms",
- "default_buying_terms",
- "default_warehouse_for_sales_return",
- "default_in_transit_warehouse",
- "column_break_10",
- "country",
- "create_chart_of_accounts_based_on",
- "chart_of_accounts",
- "existing_company",
"tax_id",
+ "domain",
"date_of_establishment",
+ "parent_company",
+ "company_info",
+ "company_logo",
+ "date_of_incorporation",
+ "phone_no",
+ "email",
+ "company_description",
+ "column_break1",
+ "date_of_commencement",
+ "fax",
+ "website",
+ "address_html",
+ "section_break_28",
+ "create_chart_of_accounts_based_on",
+ "existing_company",
+ "column_break_26",
+ "chart_of_accounts",
+ "charts_section",
"sales_settings",
- "monthly_sales_target",
+ "default_buying_terms",
"sales_monthly_history",
- "column_break_goals",
- "transactions_annual_history",
+ "monthly_sales_target",
"total_monthly_sales",
+ "column_break_goals",
+ "default_selling_terms",
+ "default_warehouse_for_sales_return",
+ "credit_limit",
+ "transactions_annual_history",
+ "hr_settings_section",
+ "default_holiday_list",
+ "default_expense_claim_payable_account",
+ "column_break_10",
+ "default_employee_advance_account",
+ "default_payroll_payable_account",
"default_settings",
"default_bank_account",
"default_cash_account",
@@ -52,24 +67,20 @@
"column_break0",
"allow_account_creation_against_child_company",
"default_payable_account",
- "default_employee_advance_account",
"default_expense_account",
"default_income_account",
"default_deferred_revenue_account",
"default_deferred_expense_account",
- "default_payroll_payable_account",
- "default_expense_claim_payable_account",
"default_discount_account",
- "section_break_22",
- "cost_center",
- "column_break_26",
- "credit_limit",
"payment_terms",
+ "cost_center",
+ "default_finance_book",
"auto_accounting_for_stock_settings",
"enable_perpetual_inventory",
"enable_perpetual_inventory_for_non_stock_items",
"default_inventory_account",
"stock_adjustment_account",
+ "default_in_transit_warehouse",
"column_break_32",
"stock_received_but_not_billed",
"service_received_but_not_billed",
@@ -79,25 +90,14 @@
"depreciation_expense_account",
"series_for_depreciation_entry",
"expenses_included_in_asset_valuation",
+ "repair_and_maintenance_account",
"column_break_40",
"disposal_account",
"depreciation_cost_center",
"capital_work_in_progress_account",
- "repair_and_maintenance_account",
"asset_received_but_not_billed",
"budget_detail",
"exception_budget_approver_role",
- "company_info",
- "company_logo",
- "date_of_incorporation",
- "address_html",
- "date_of_commencement",
- "phone_no",
- "fax",
- "email",
- "website",
- "column_break1",
- "company_description",
"registration_info",
"registration_details",
"lft",
@@ -128,12 +128,6 @@
"reqd": 1
},
{
- "depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")",
- "fieldname": "change_abbr",
- "fieldtype": "Button",
- "label": "Change Abbreviation"
- },
- {
"bold": 1,
"default": "0",
"fieldname": "is_group",
@@ -176,10 +170,9 @@
"label": "Company Description"
},
{
- "collapsible": 1,
"fieldname": "sales_settings",
"fieldtype": "Section Break",
- "label": "Sales Settings"
+ "label": "Buying & Selling Settings"
},
{
"fieldname": "sales_monthly_history",
@@ -443,10 +436,6 @@
"options": "Account"
},
{
- "fieldname": "section_break_22",
- "fieldtype": "Section Break"
- },
- {
"depends_on": "eval:!doc.__islocal",
"fieldname": "cost_center",
"fieldtype": "Link",
@@ -456,10 +445,6 @@
"options": "Cost Center"
},
{
- "fieldname": "column_break_26",
- "fieldtype": "Column Break"
- },
- {
"depends_on": "eval:!doc.__islocal",
"fieldname": "credit_limit",
"fieldtype": "Currency",
@@ -589,10 +574,10 @@
},
{
"collapsible": 1,
- "description": "For reference only.",
+ "depends_on": "eval: doc.docstatus == 0 && doc.__islocal != 1",
"fieldname": "company_info",
"fieldtype": "Section Break",
- "label": "Company Info"
+ "label": "Address & Contact"
},
{
"fieldname": "date_of_incorporation",
@@ -741,6 +726,20 @@
"fieldtype": "Link",
"label": "Repair and Maintenance Account",
"options": "Account"
+ },
+ {
+ "fieldname": "section_break_28",
+ "fieldtype": "Section Break",
+ "label": "Chart of Accounts"
+ },
+ {
+ "fieldname": "hr_settings_section",
+ "fieldtype": "Section Break",
+ "label": "HR & Payroll Settings"
+ },
+ {
+ "fieldname": "column_break_26",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-building",
@@ -748,7 +747,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2021-05-12 16:51:08.187233",
+ "modified": "2021-07-12 11:27:06.353860",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index e6ce3c8..2f37778 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -84,8 +84,6 @@
"oldfieldtype": "Section Break"
},
{
- "allow_on_submit": 1,
- "default": "{purpose}",
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
@@ -630,7 +628,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-08-17 20:16:12.737743",
+ "modified": "2021-08-20 19:19:31.514846",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 90a33d3..0b4592c 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -58,6 +58,7 @@
self.validate_posting_time()
self.validate_purpose()
+ self.set_title()
self.validate_item()
self.validate_customer_provided_item()
self.validate_qty()
@@ -1608,6 +1609,14 @@
return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
+ def set_title(self):
+ if frappe.flags.in_import and self.title:
+ # Allow updating title during data import/update
+ return
+
+ self.title = self.purpose
+
+
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, string_types):
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index a0fbcec..c72073c 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -278,6 +278,10 @@
else:
args.uom = item.stock_uom
+ if (args.get("batch_no") and
+ item.name != frappe.get_cached_value('Batch', args.get("batch_no"), 'item')):
+ args['batch_no'] = ''
+
out = frappe._dict({
"item_code": item.name,
"item_name": item.item_name,
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js
index 6b384e2..78afe6d 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.js
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.js
@@ -37,11 +37,25 @@
default: "",
},
{
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1,
+ },
+ {
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
- options:"Warehouse",
+ options: "Warehouse",
default: "",
+ get_query: function() {
+ const company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: { 'company': company }
+ }
+ }
},
{
fieldname: "from_date",
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index d62abed..a1e1e7f 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -1,14 +1,15 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
+import datetime
-from __future__ import unicode_literals
import frappe
from frappe import _, scrub
-from frappe.utils import getdate, flt
+from frappe.utils import getdate, get_quarter_start, get_first_day_of_week
+from frappe.utils import get_first_day as get_first_day_of_month
+
from erpnext.stock.report.stock_balance.stock_balance import (get_items, get_stock_ledger_entries, get_item_details)
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.utils import is_reposting_item_valuation_in_progress
-from six import iteritems
def execute(filters=None):
is_reposting_item_valuation_in_progress()
@@ -71,7 +72,8 @@
def get_period_date_ranges(filters):
from dateutil.relativedelta import relativedelta
- from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
+ from_date = round_down_to_nearest_frequency(filters.from_date, filters.range)
+ to_date = getdate(filters.to_date)
increment = {
"Monthly": 1,
@@ -97,6 +99,31 @@
return periodic_daterange
+
+def round_down_to_nearest_frequency(date: str, frequency: str) -> datetime.datetime:
+ """Rounds down the date to nearest frequency unit.
+ example:
+
+ >>> round_down_to_nearest_frequency("2021-02-21", "Monthly")
+ datetime.datetime(2021, 2, 1)
+
+ >>> round_down_to_nearest_frequency("2021-08-21", "Yearly")
+ datetime.datetime(2021, 1, 1)
+ """
+
+ def _get_first_day_of_fiscal_year(date):
+ fiscal_year = get_fiscal_year(date)
+ return fiscal_year and fiscal_year[1] or date
+
+ round_down_function = {
+ "Monthly": get_first_day_of_month,
+ "Quarterly": get_quarter_start,
+ "Weekly": get_first_day_of_week,
+ "Yearly": _get_first_day_of_fiscal_year,
+ }.get(frequency, getdate)
+ return round_down_function(date)
+
+
def get_period(posting_date, filters):
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
@@ -177,7 +204,7 @@
periodic_data = get_periodic_data(sle, filters)
ranges = get_period_date_ranges(filters)
- for dummy, item_data in iteritems(item_details):
+ for dummy, item_data in item_details.items():
row = {
"name": item_data.name,
"item_name": item_data.item_name,
diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
new file mode 100644
index 0000000..00e268b
--- /dev/null
+++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
@@ -0,0 +1,35 @@
+import datetime
+import unittest
+
+from frappe import _dict
+from erpnext.accounts.utils import get_fiscal_year
+
+from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges
+
+
+class TestStockAnalyticsReport(unittest.TestCase):
+ def test_get_period_date_ranges(self):
+
+ filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06")
+
+ ranges = get_period_date_ranges(filters)
+
+ expected_ranges = [
+ [datetime.date(2020, 12, 1), datetime.date(2020, 12, 31)],
+ [datetime.date(2021, 1, 1), datetime.date(2021, 1, 31)],
+ [datetime.date(2021, 2, 1), datetime.date(2021, 2, 6)],
+ ]
+
+ self.assertEqual(ranges, expected_ranges)
+
+ def test_get_period_date_ranges_yearly(self):
+
+ filters = _dict(range="Yearly", from_date="2021-01-28", to_date="2021-02-06")
+
+ ranges = get_period_date_ranges(filters)
+ first_date = get_fiscal_year("2021-01-28")[1]
+ expected_ranges = [
+ [first_date, datetime.date(2021, 2, 6)],
+ ]
+
+ self.assertEqual(ranges, expected_ranges)
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 8909f21..b6923e9 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -23,6 +23,7 @@
conversion_factors = []
if opening_row:
data.append(opening_row)
+ conversion_factors.append(0)
actual_qty = stock_value = 0