Merge pull request #26588 from deepeshgarg007/tax_flow_fix_pre_release
fix: Additional discount calculations in Invoices
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 0c96d32..1166549 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.6.0'
+__version__ = '13.7.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 51f18a5..6f362c1 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -667,6 +667,7 @@
{
"fieldname": "base_paid_amount_after_tax",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Paid Amount After Tax (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
@@ -693,21 +694,25 @@
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
"fieldname": "received_amount_after_tax",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Received Amount After Tax",
- "options": "paid_to_account_currency"
+ "options": "paid_to_account_currency",
+ "read_only": 1
},
{
"depends_on": "doc.received_amount",
"fieldname": "base_received_amount_after_tax",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Received Amount After Tax (Company Currency)",
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-06-22 20:37:06.154206",
+ "modified": "2021-07-09 08:58:15.008761",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 0c21aae..7f665db 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -404,9 +404,15 @@
if not self.advance_tax_account:
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
- reference_doclist = []
net_total = self.paid_amount
- included_in_paid_amount = 0
+
+ for reference in self.get("references"):
+ net_total_for_tds = 0
+ if reference.reference_doctype == 'Purchase Order':
+ net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
+
+ if net_total_for_tds:
+ net_total = net_total_for_tds
# Adding args as purchase invoice to get TDS amount
args = frappe._dict({
@@ -423,7 +429,7 @@
return
tax_withholding_details.update({
- 'included_in_paid_amount': included_in_paid_amount,
+ 'add_deduct_tax': 'Add',
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
})
@@ -512,16 +518,19 @@
self.unallocated_amount = 0
if self.party:
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
+ included_taxes = self.get_included_taxes()
if self.payment_type == "Receive" \
- and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \
- and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate):
- self.unallocated_amount = (self.received_amount_after_tax + total_deductions -
+ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
+ and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
+ self.unallocated_amount = (self.received_amount + total_deductions -
self.base_total_allocated_amount) / self.source_exchange_rate
+ self.unallocated_amount -= included_taxes
elif self.payment_type == "Pay" \
- and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \
- and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate):
- self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions +
+ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
+ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
+ self.unallocated_amount = (self.base_paid_amount - (total_deductions +
self.base_total_allocated_amount)) / self.target_exchange_rate
+ self.unallocated_amount -= included_taxes
def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
@@ -530,17 +539,29 @@
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
if self.payment_type == "Receive":
- self.difference_amount = base_party_amount - self.base_received_amount_after_tax
+ self.difference_amount = base_party_amount - self.base_received_amount
elif self.payment_type == "Pay":
- self.difference_amount = self.base_paid_amount_after_tax - base_party_amount
+ self.difference_amount = self.base_paid_amount - base_party_amount
else:
- self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax)
+ self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
+ included_taxes = self.get_included_taxes()
- self.difference_amount = flt(self.difference_amount - total_deductions,
+ self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes,
self.precision("difference_amount"))
+ def get_included_taxes(self):
+ included_taxes = 0
+ for tax in self.get('taxes'):
+ if tax.included_in_paid_amount:
+ if tax.add_deduct_tax == 'Add':
+ included_taxes += tax.base_tax_amount
+ else:
+ included_taxes -= tax.base_tax_amount
+
+ return included_taxes
+
# Paid amount is auto allocated in the reference document by default.
# Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast
def clear_unallocated_reference_document_rows(self):
@@ -683,8 +704,8 @@
"account": self.paid_from,
"account_currency": self.paid_from_account_currency,
"against": self.party if self.payment_type=="Pay" else self.paid_to,
- "credit_in_account_currency": self.paid_amount_after_tax,
- "credit": self.base_paid_amount_after_tax,
+ "credit_in_account_currency": self.paid_amount,
+ "credit": self.base_paid_amount,
"cost_center": self.cost_center
}, item=self)
)
@@ -694,8 +715,8 @@
"account": self.paid_to,
"account_currency": self.paid_to_account_currency,
"against": self.party if self.payment_type=="Receive" else self.paid_from,
- "debit_in_account_currency": self.received_amount_after_tax,
- "debit": self.base_received_amount_after_tax,
+ "debit_in_account_currency": self.received_amount,
+ "debit": self.base_received_amount,
"cost_center": self.cost_center
}, item=self)
)
@@ -708,35 +729,42 @@
if self.payment_type in ('Pay', 'Internal Transfer'):
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
+ against = self.party or self.paid_from
elif self.payment_type == 'Receive':
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
+ against = self.party or self.paid_to
payment_or_advance_account = self.get_party_account_for_taxes()
+ tax_amount = d.tax_amount
+ base_tax_amount = d.base_tax_amount
+
+ if self.advance_tax_account:
+ tax_amount = -1 * tax_amount
+ base_tax_amount = -1 * base_tax_amount
gl_entries.append(
self.get_gl_dict({
"account": d.account_head,
- "against": self.party if self.payment_type=="Receive" else self.paid_from,
- dr_or_cr: d.base_tax_amount,
- dr_or_cr + "_in_account_currency": d.base_tax_amount
+ "against": against,
+ dr_or_cr: tax_amount,
+ dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": d.cost_center
}, account_currency, item=d))
#Intentionally use -1 to get net values in party account
- gl_entries.append(
- self.get_gl_dict({
- "account": payment_or_advance_account,
- "against": self.party if self.payment_type=="Receive" else self.paid_from,
- dr_or_cr: -1 * d.base_tax_amount,
- dr_or_cr + "_in_account_currency": -1*d.base_tax_amount
- if account_currency==self.company_currency
- else d.tax_amount,
- "cost_center": self.cost_center,
- "party_type": self.party_type,
- "party": self.party
- }, account_currency, item=d))
+ if not d.included_in_paid_amount or self.advance_tax_account:
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": payment_or_advance_account,
+ "against": against,
+ dr_or_cr: -1 * tax_amount,
+ dr_or_cr + "_in_account_currency": -1 * base_tax_amount
+ if account_currency==self.company_currency
+ else d.tax_amount,
+ "cost_center": self.cost_center,
+ }, account_currency, item=d))
def add_deductions_gl_entries(self, gl_entries):
for d in self.get("deductions"):
@@ -760,9 +788,9 @@
if self.advance_tax_account:
return self.advance_tax_account
elif self.payment_type == 'Receive':
- return self.paid_from
- elif self.payment_type in ('Pay', 'Internal Transfer'):
return self.paid_to
+ elif self.payment_type in ('Pay', 'Internal Transfer'):
+ return self.paid_from
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
@@ -1634,12 +1662,6 @@
if dt == "Employee Advance":
paid_amount = received_amount * doc.get('exchange_rate', 1)
- if dt == "Purchase Order" and doc.apply_tds:
- if party_account_currency == bank.account_currency:
- paid_amount = received_amount = doc.base_net_total
- else:
- paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1)
-
return paid_amount, received_amount
def apply_early_payment_discount(paid_amount, received_amount, doc):
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index e15715d..6b9df41 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -75,7 +75,8 @@
select voucher_no, credit
from `tabGL Entry`
where party in (%s) and credit > 0
- and company=%s and posting_date between %s and %s
+ and company=%s and is_cancelled = 0
+ and posting_date between %s and %s
""", (supplier, company, from_date, to_date), as_dict=1)
supplier_credit_amount = flt(sum(d.credit for d in entries))
diff --git a/erpnext/change_log/v13/v13_7_0.md b/erpnext/change_log/v13/v13_7_0.md
new file mode 100644
index 0000000..589f610
--- /dev/null
+++ b/erpnext/change_log/v13/v13_7_0.md
@@ -0,0 +1,69 @@
+# Version 13.7.0 Release Notes
+
+### Features & Enhancements
+- Optionally allow rejected quality inspection on submission ([#26133](https://github.com/frappe/erpnext/pull/26133))
+- Bootstrapped GST Setup for India ([#25415](https://github.com/frappe/erpnext/pull/25415))
+- Fetching details from supplier/customer groups ([#26454](https://github.com/frappe/erpnext/pull/26454))
+- Provision to make subcontracted purchase order from the production plan ([#26240](https://github.com/frappe/erpnext/pull/26240))
+- Optimized code for reposting item valuation ([#26432](https://github.com/frappe/erpnext/pull/26432))
+
+### Fixes
+- Auto process deferred accounting for multi-company setup ([#26277](https://github.com/frappe/erpnext/pull/26277))
+- Error while fetching item taxes ([#26218](https://github.com/frappe/erpnext/pull/26218))
+- Validation check for batch for stock reconciliation type in stock entry(bp #26370 ) ([#26488](https://github.com/frappe/erpnext/pull/26488))
+- Error popup for COA errors ([#26358](https://github.com/frappe/erpnext/pull/26358))
+- Precision for expected values in payment entry test ([#26394](https://github.com/frappe/erpnext/pull/26394))
+- Bank statement import ([#26287](https://github.com/frappe/erpnext/pull/26287))
+- LMS progress issue ([#26253](https://github.com/frappe/erpnext/pull/26253))
+- Paging buttons not working on item group portal page ([#26497](https://github.com/frappe/erpnext/pull/26497))
+- Omit item discount amount for e-invoicing ([#26353](https://github.com/frappe/erpnext/pull/26353))
+- Validate LCV for Invoices without Update Stock ([#26333](https://github.com/frappe/erpnext/pull/26333))
+- Remove cancelled entries in consolidated financial statements ([#26331](https://github.com/frappe/erpnext/pull/26331))
+- Fetching employee in payroll entry ([#26271](https://github.com/frappe/erpnext/pull/26271))
+- To fetch the correct field in Tax Rule ([#25927](https://github.com/frappe/erpnext/pull/25927))
+- Order and time of operations in multilevel BOM work order ([#25886](https://github.com/frappe/erpnext/pull/25886))
+- Fixed Budget Variance Graph color from all black to default ([#26368](https://github.com/frappe/erpnext/pull/26368))
+- TDS computation summary shows cancelled invoices (#26456) ([#26486](https://github.com/frappe/erpnext/pull/26486))
+- Do not consider cancelled entries in party dashboard ([#26231](https://github.com/frappe/erpnext/pull/26231))
+- Add validation for 'for_qty' else throws errors ([#25829](https://github.com/frappe/erpnext/pull/25829))
+- Move the rename abbreviation job to long queue (#26434) ([#26462](https://github.com/frappe/erpnext/pull/26462))
+- Query for Training Event ([#26388](https://github.com/frappe/erpnext/pull/26388))
+- Item group portal issues (backport) ([#26493](https://github.com/frappe/erpnext/pull/26493))
+- When lead is created with mobile_no, mobile_no value gets lost ([#26298](https://github.com/frappe/erpnext/pull/26298))
+- WIP needs to be set before submit on skip_transfer (bp #26499) ([#26507](https://github.com/frappe/erpnext/pull/26507))
+- Incorrect valuation rate in stock reconciliation ([#26259](https://github.com/frappe/erpnext/pull/26259))
+- Precision rate for packed items in internal transfers ([#26046](https://github.com/frappe/erpnext/pull/26046))
+- Changed profitability analysis report width ([#26165](https://github.com/frappe/erpnext/pull/26165))
+- Unable to download GSTR-1 json ([#26468](https://github.com/frappe/erpnext/pull/26468))
+- Unallocated amount in Payment Entry after taxes ([#26472](https://github.com/frappe/erpnext/pull/26472))
+- Include Stock Reco logic in `update_qty_in_future_sle` ([#26158](https://github.com/frappe/erpnext/pull/26158))
+- Update cost not working in the draft BOM ([#26279](https://github.com/frappe/erpnext/pull/26279))
+- Cancellation of Loan Security Pledges ([#26252](https://github.com/frappe/erpnext/pull/26252))
+- fix(e-invoicing): allow export invoice even if no taxes applied (#26363) ([#26405](https://github.com/frappe/erpnext/pull/26405))
+- Delete accounts (an empty file) ([#25323](https://github.com/frappe/erpnext/pull/25323))
+- Errors on parallel requests creation of company for India ([#26470](https://github.com/frappe/erpnext/pull/26470))
+- Incorrect bom no added for non-variant items on variant boms ([#26320](https://github.com/frappe/erpnext/pull/26320))
+- Incorrect discount amount on amended document ([#26466](https://github.com/frappe/erpnext/pull/26466))
+- Added a message to enable appointment booking if disabled ([#26334](https://github.com/frappe/erpnext/pull/26334))
+- fix(pos): taxes amount in pos item cart ([#26411](https://github.com/frappe/erpnext/pull/26411))
+- Track changes on batch ([#26382](https://github.com/frappe/erpnext/pull/26382))
+- Stock entry with putaway rule not working ([#26350](https://github.com/frappe/erpnext/pull/26350))
+- Only "Tax" type accounts should be shown for selection in GST Settings ([#26300](https://github.com/frappe/erpnext/pull/26300))
+- Added permission for employee to book appointment ([#26255](https://github.com/frappe/erpnext/pull/26255))
+- Allow to make job card without employee ([#26312](https://github.com/frappe/erpnext/pull/26312))
+- Project Portal Enhancements ([#26290](https://github.com/frappe/erpnext/pull/26290))
+- BOM stock report not working ([#26332](https://github.com/frappe/erpnext/pull/26332))
+- Order Items by weightage in the web items query ([#26284](https://github.com/frappe/erpnext/pull/26284))
+- Removed values out of sync validation from stock transactions ([#26226](https://github.com/frappe/erpnext/pull/26226))
+- Payroll-entry minor fix ([#26349](https://github.com/frappe/erpnext/pull/26349))
+- Allow user to change the To Date in the blanket order even after submit of order ([#26241](https://github.com/frappe/erpnext/pull/26241))
+- Value fetching for custom field in POS ([#26367](https://github.com/frappe/erpnext/pull/26367))
+- Iteration through accounts only when accounts exist ([#26391](https://github.com/frappe/erpnext/pull/26391))
+- Employee Inactive status implications ([#26244](https://github.com/frappe/erpnext/pull/26244))
+- Multi-currency issue ([#26458](https://github.com/frappe/erpnext/pull/26458))
+- FG item not fetched in manufacture entry ([#26509](https://github.com/frappe/erpnext/pull/26509))
+- Set query for training events ([#26303](https://github.com/frappe/erpnext/pull/26303))
+- Fetch batch items in stock reconciliation ([#26213](https://github.com/frappe/erpnext/pull/26213))
+- Employee selection not working in payroll entry ([#26278](https://github.com/frappe/erpnext/pull/26278))
+- POS item cart dom updates (#26459) ([#26461](https://github.com/frappe/erpnext/pull/26461))
+- dunning calculation of grand total when rate of interest is 0% ([#26285](https://github.com/frappe/erpnext/pull/26285))
\ No newline at end of file
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1c086e9..5d30b65 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -751,11 +751,11 @@
account_currency = get_account_currency(tax.account_head)
if self.doctype == "Purchase Invoice":
- dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
- rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
- else:
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+ else:
+ dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+ rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
unallocated_amount = tax.tax_amount - tax.allocated_amount
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index c32a8a9..9da461f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -713,7 +713,8 @@
"conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
"conversion_factor": args.get("conversion_factor") or 1,
"plc_conversion_rate": 1,
- "ignore_party": True
+ "ignore_party": True,
+ "ignore_conversion_rate": True
})
item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
out = frappe._dict()
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 68de0b2..bf1ccb7 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -513,6 +513,60 @@
work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
+ def test_batch_size_for_fg_item(self):
+ fg_item = "Test Batch Size Item For BOM 3"
+ rm1 = "Test Batch Size Item RM 1 For BOM 3"
+
+ frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+ for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]:
+ item_args = {
+ "include_item_in_manufacturing": 1,
+ "is_stock_item": 1
+ }
+
+ if item == fg_item:
+ item_args['has_batch_no'] = 1
+ item_args['create_new_batch'] = 1
+ item_args['batch_number_series'] = 'TBSI3.#####'
+
+ make_item(item, item_args)
+
+ bom_name = frappe.db.get_value("BOM",
+ {"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
+
+ if not bom_name:
+ bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
+ bom.save()
+ bom.submit()
+ bom_name = bom.name
+
+ work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+ for row in ste1.get('items'):
+ if row.is_finished_item:
+ self.assertEqual(row.item_code, fg_item)
+
+ work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+ frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1)
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+ for row in ste1.get('items'):
+ if row.is_finished_item:
+ self.assertEqual(row.item_code, fg_item)
+
+ work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(),
+ qty=30, do_not_save = True)
+ work_order.batch_size = 10
+ work_order.insert()
+ work_order.submit()
+ self.assertEqual(work_order.has_batch_no, 1)
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
+ for row in ste1.get('items'):
+ if row.is_finished_item:
+ self.assertEqual(row.item_code, fg_item)
+ self.assertEqual(row.qty, 10)
+
+ frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 779ae42..0a8e532 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -239,7 +239,7 @@
self.create_serial_no_batch_no()
def on_submit(self):
- if not self.wip_warehouse:
+ if not self.wip_warehouse and not self.skip_transfer:
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
if not self.fg_warehouse:
frappe.throw(_("For Warehouse is required before Submit"))
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 29376f0..f63c7ed 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -291,3 +291,4 @@
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
erpnext.patches.v13_0.update_job_card_details
erpnext.patches.v13_0.update_level_in_bom #1234sswef
+erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
new file mode 100644
index 0000000..48999e6
--- /dev/null
+++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe.utils import cstr, flt, cint
+from erpnext.stock.stock_ledger import make_sl_entries
+from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
+
+def execute():
+ if not frappe.db.has_column('Work Order', 'has_batch_no'):
+ return
+
+ if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
+ return
+
+ frappe.reload_doc('manufacturing', 'doctype', 'work_order')
+ filters = {
+ 'docstatus': 1,
+ 'produced_qty': ('>', 0),
+ 'creation': ('>=', '2021-06-29 00:00:00'),
+ 'has_batch_no': 1
+ }
+
+ fields = ['name', 'production_item']
+
+ work_orders = [d.name for d in frappe.get_all('Work Order', filters = filters, fields=fields)]
+
+ if not work_orders:
+ return
+
+ repost_stock_entries = []
+ stock_entries = frappe.db.sql_list('''
+ SELECT
+ se.name
+ FROM
+ `tabStock Entry` se
+ WHERE
+ se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in {work_orders}
+ and not exists(
+ select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1
+ )
+ Order BY
+ se.posting_date, se.posting_time
+ '''.format(work_orders=tuple(work_orders)))
+
+ if stock_entries:
+ print('Length of stock entries', len(stock_entries))
+
+ for stock_entry in stock_entries:
+ doc = frappe.get_doc('Stock Entry', stock_entry)
+ doc.set_work_order_details()
+ doc.load_items_from_bom()
+ doc.calculate_rate_and_amount()
+ set_expense_account(doc)
+ doc.make_batches('t_warehouse')
+
+ if doc.docstatus == 0:
+ doc.save()
+ else:
+ repost_stock_entry(doc)
+ repost_stock_entries.append(doc)
+
+ for repost_doc in repost_stock_entries:
+ repost_future_sle_and_gle(repost_doc)
+
+def set_expense_account(doc):
+ for row in doc.items:
+ if row.is_finished_item and not row.expense_account:
+ row.expense_account = frappe.get_cached_value('Company', doc.company, 'stock_adjustment_account')
+
+def repost_stock_entry(doc):
+ doc.db_update()
+ for child_row in doc.items:
+ if child_row.is_finished_item:
+ child_row.db_update()
+
+ sl_entries = []
+ finished_item_row = doc.get_finished_item_row()
+ get_sle_for_target_warehouse(doc, sl_entries, finished_item_row)
+
+ if sl_entries:
+ try:
+ make_sl_entries(sl_entries, True)
+ except Exception:
+ print(f'SLE entries not posted for the stock entry {doc.name}')
+ traceback = frappe.get_traceback()
+ frappe.log_error(traceback)
+
+def get_sle_for_target_warehouse(doc, sl_entries, finished_item_row):
+ for d in doc.get('items'):
+ if cstr(d.t_warehouse) and finished_item_row and d.name == finished_item_row.name:
+ sle = doc.get_sl_entries(d, {
+ "warehouse": cstr(d.t_warehouse),
+ "actual_qty": flt(d.transfer_qty),
+ "incoming_rate": flt(d.valuation_rate)
+ })
+
+ sle.recalculate_rate = 1
+ sl_entries.append(sle)
+
+def repost_future_sle_and_gle(doc):
+ args = frappe._dict({
+ "posting_date": doc.posting_date,
+ "posting_time": doc.posting_time,
+ "voucher_type": doc.doctype,
+ "voucher_no": doc.name,
+ "company": doc.company
+ })
+
+ create_repost_item_valuation_entry(args)
\ No newline at end of file
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index d77eb2c..211b94a 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -2,6 +2,7 @@
from frappe.utils import cint
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
from erpnext.shopping_cart.product_info import get_product_info_for_website
+from erpnext.setup.doctype.item_group.item_group import get_child_groups
def get_field_filter_data():
product_settings = get_product_settings()
@@ -89,6 +90,7 @@
def get_products_html_for_website(field_filters=None, attribute_filters=None):
field_filters = frappe.parse_json(field_filters)
attribute_filters = frappe.parse_json(attribute_filters)
+ set_item_group_filters(field_filters)
items = get_products_for_website(field_filters, attribute_filters)
html = ''.join(get_html_for_items(items))
@@ -98,6 +100,10 @@
return html
+def set_item_group_filters(field_filters):
+ if 'item_group' in field_filters:
+ field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
+
def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
items = []
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 53d5278..7b651fc 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -67,6 +67,8 @@
calculate_discount_amount: function() {
if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
+ this.calculate_item_values();
+ this.calculate_net_total();
this.set_discount_amount();
this.apply_discount_amount();
}
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 5f9d5ed..9265460 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -12,7 +12,10 @@
from frappe.utils import today
def setup(company=None, patch=True):
- setup_company_independent_fixtures(patch=patch)
+ # Company independent fixtures should be called only once at the first company setup
+ if frappe.db.count('Company', {'country': 'India'}) <=1:
+ setup_company_independent_fixtures(patch=patch)
+
if not patch:
make_fixtures(company)
@@ -122,10 +125,12 @@
def make_property_setters(patch=False):
# GST rules do not allow for an invoice no. bigger than 16 characters
journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
+ sales_invoice_series = ['SINV-.YY.-', 'SRET-.YY.-', ''] + frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n")
+ purchase_invoice_series = ['PINV-.YY.-', 'PRET-.YY.-', ''] + frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n")
if not patch:
- make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
- make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+ make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '')
+ make_property_setter('Purchase Invoice', 'naming_series', 'options', '\n'.join(purchase_invoice_series), '')
make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
def make_custom_fields(update=True):
@@ -786,7 +791,7 @@
doc.flags.ignore_mandatory = True
doc.insert()
else:
- doc = frappe.get_doc("Tax Withholding Category", d.get("name"))
+ doc = frappe.get_doc("Tax Withholding Category", d.get("name"), for_update=True)
if accounts:
doc.append("accounts", accounts[0])
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 1096159..cfcb8c3 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -584,7 +584,7 @@
def get_json(filters, report_name, data):
filters = json.loads(filters)
report_data = json.loads(data)
- gstin = get_company_gstin_number(filters["company"], filters["company_address"])
+ gstin = get_company_gstin_number(filters.get("company"), filters.get("company_address"))
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 38508c2..f7b2c1d 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -965,8 +965,23 @@
});
}
+ attach_refresh_field_event(frm) {
+ $(frm.wrapper).off('refresh-fields');
+ $(frm.wrapper).on('refresh-fields', () => {
+ if (frm.doc.items.length) {
+ frm.doc.items.forEach(item => {
+ this.update_item_html(item);
+ });
+ }
+ this.update_totals_section(frm);
+ });
+ }
+
load_invoice() {
const frm = this.events.get_frm();
+
+ this.attach_refresh_field_event(frm);
+
this.fetch_customer_details(frm.doc.customer).then(() => {
this.events.customer_details_updated(this.customer_info);
this.update_customer_section();
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 36a7d20..8755125 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -291,7 +291,7 @@
cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name')
if cash and self.default_cash_account \
and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}):
- mode_of_payment = frappe.get_doc('Mode of Payment', cash)
+ mode_of_payment = frappe.get_doc('Mode of Payment', cash, for_update=True)
mode_of_payment.append('accounts', {
'company': self.name,
'default_account': self.default_cash_account
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 1c72ceb..5fcad00 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -87,8 +87,8 @@
if not field_filters:
field_filters = {}
- # Ensure the query remains within current item group
- field_filters['item_group'] = self.name
+ # Ensure the query remains within current item group & sub group
+ field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)]
engine = ProductQuery()
context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 55f2ebb..5f31d9c 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -133,6 +133,6 @@
def get_repost_item_valuation_entries():
return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
- WHERE status != 'Completed' and creation <= %s and docstatus = 1
+ WHERE status in ('Queued', 'In Progress') and creation <= %s and docstatus = 1
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
""", now(), as_dict=1)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 90b81dd..872b1d0 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -719,6 +719,10 @@
frappe.throw(_("Multiple items cannot be marked as finished item"))
if self.purpose == "Manufacture":
+ if not finished_items:
+ frappe.throw(_('Finished Good has not set in the stock entry {0}')
+ .format(self.name))
+
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
"overproduction_percentage_for_work_order"))
@@ -1090,13 +1094,13 @@
"is_finished_item": 1
}
- if self.work_order and self.pro_doc.has_batch_no:
+ if self.work_order and self.pro_doc.has_batch_no and cint(frappe.db.get_single_value('Manufacturing Settings',
+ 'make_serial_no_batch_from_work_order', cache=True)):
self.set_batchwise_finished_goods(args, item)
else:
- self.add_finisged_goods(args, item)
+ self.add_finished_goods(args, item)
def set_batchwise_finished_goods(self, args, item):
- qty = flt(self.fg_completed_qty)
filters = {
"reference_name": self.pro_doc.name,
"reference_doctype": self.pro_doc.doctype,
@@ -1105,7 +1109,17 @@
fields = ["qty_to_produce as qty", "produced_qty", "name"]
- for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"):
+ data = frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc")
+
+ if not data:
+ self.add_finished_goods(args, item)
+ else:
+ self.add_batchwise_finished_good(data, args, item)
+
+ def add_batchwise_finished_good(self, data, args, item):
+ qty = flt(self.fg_completed_qty)
+
+ for row in data:
batch_qty = flt(row.qty) - flt(row.produced_qty)
if not batch_qty:
continue
@@ -1121,9 +1135,9 @@
args["qty"] = fg_qty
args["batch_no"] = row.name
- self.add_finisged_goods(args, item)
+ self.add_finished_goods(args, item)
- def add_finisged_goods(self, args, item):
+ def add_finished_goods(self, args, item):
self.add_to_stock_entry_detail({
item.name: args
}, bom_no = self.bom_no)
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 cb939e6..93482e8 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -89,17 +89,16 @@
if item_det.is_stock_item != 1:
frappe.throw(_("Item {0} must be a stock Item").format(self.item_code))
- # check if batch number is required
- if self.voucher_type != 'Stock Reconciliation':
- if item_det.has_batch_no == 1:
- batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name
- if not self.batch_no:
- frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
- 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))
+ # check if batch number is valid
+ if item_det.has_batch_no == 1:
+ batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name
+ if not self.batch_no:
+ frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
+ 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 == 0:
- frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
+ elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0:
+ frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
if item_det.has_variants:
frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index a01db80..349e59f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -17,6 +17,14 @@
}
}
});
+ frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
+ var item = locals[cdt][cdn];
+ return {
+ filters: {
+ 'item': item.item_code
+ }
+ };
+ });
if (frm.doc.company) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 84cdc49..c192582 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -16,6 +16,7 @@
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+
class TestStockReconciliation(unittest.TestCase):
@classmethod
def setUpClass(self):
@@ -352,6 +353,26 @@
dn2.cancel()
pr1.cancel()
+ def test_valid_batch(self):
+ create_batch_item_with_batch("Testing Batch Item 1", "001")
+ create_batch_item_with_batch("Testing Batch Item 2", "002")
+ sr = create_stock_reconciliation(item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002"
+ , do_not_submit=True)
+ self.assertRaises(frappe.ValidationError, sr.submit)
+
+def create_batch_item_with_batch(item_name, batch_id):
+ batch_item_doc = create_item(item_name, is_stock_item=1)
+ if not batch_item_doc.has_batch_no:
+ batch_item_doc.has_batch_no = 1
+ batch_item_doc.create_new_batch = 1
+ batch_item_doc.save(ignore_permissions=True)
+
+ if not frappe.db.exists('Batch', batch_id):
+ b = frappe.new_doc('Batch')
+ b.item = item_name
+ b.batch_id = batch_id
+ b.save()
+
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index ca174a3..4657700 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -441,7 +441,7 @@
if item_tax_templates is None:
item_tax_templates = {}
-
+
if item_rates is None:
item_rates = {}
@@ -807,10 +807,14 @@
def validate_conversion_rate(args, meta):
from erpnext.controllers.accounts_controller import validate_conversion_rate
- if (not args.conversion_rate
- and args.currency==frappe.get_cached_value('Company', args.company, "default_currency")):
+ company_currency = frappe.get_cached_value('Company', args.company, "default_currency")
+ if (not args.conversion_rate and args.currency==company_currency):
args.conversion_rate = 1.0
+ if (not args.ignore_conversion_rate and args.conversion_rate == 1 and args.currency!=company_currency):
+ args.conversion_rate = get_exchange_rate(args.currency,
+ company_currency, args.transaction_date, "for_buying") or 1.0
+
# validate currency conversion rate
validate_conversion_rate(args.currency, args.conversion_rate,
meta.get_label("conversion_rate"), args.company)
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index 393c3a4..9050cc3 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -9,7 +9,7 @@
{% endblock %}
{% block page_content %}
-<div class="item-group-content" itemscope itemtype="http://schema.org/Product">
+<div class="item-group-content" itemscope itemtype="http://schema.org/Product" data-item-group="{{ name }}">
<div class="item-group-slideshow">
{% if slideshow %}<!-- slideshow -->
{{ web_block(
@@ -127,15 +127,36 @@
</script>
</div>
</div>
- <div class="row">
- <div class="col-12">
+ <div class="row mt-6">
+ <div class="col-3">
+ </div>
+ <div class="col-9">
{% if frappe.form_dict.start|int > 0 %}
- <button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
+ <button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">
+ {{ _("Prev") }}
+ </button>
{% endif %}
{% if items|length >= page_length %}
- <button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
+ <button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}"
+ style="float: right;">
+ {{ _("Next") }}
+ </button>
{% endif %}
</div>
</div>
</div>
+
+<script>
+ frappe.ready(() => {
+ $('.btn-prev, .btn-next').click((e) => {
+ const $btn = $(e.target);
+ $btn.prop('disabled', true);
+ const start = $btn.data('start');
+ let query_params = frappe.utils.get_query_params();
+ query_params.start = start;
+ let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params);
+ window.location.href = path;
+ });
+ });
+</script>
{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 0721056..1c641b5 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -124,6 +124,10 @@
attribute_filters: if_key_exists(attribute_filters)
};
+ const item_group = $(".item-group-content").data('item-group');
+ if (item_group) {
+ Object.assign(field_filters, { item_group });
+ }
return new Promise((resolve, reject) => {
frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
.then(r => {