mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-20 21:49:18 +00:00
Merge pull request #38248 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -66,7 +66,12 @@
|
|||||||
"show_balance_in_coa",
|
"show_balance_in_coa",
|
||||||
"banking_tab",
|
"banking_tab",
|
||||||
"enable_party_matching",
|
"enable_party_matching",
|
||||||
"enable_fuzzy_matching"
|
"enable_fuzzy_matching",
|
||||||
|
"reports_tab",
|
||||||
|
"remarks_section",
|
||||||
|
"general_ledger_remarks_length",
|
||||||
|
"column_break_lvjk",
|
||||||
|
"receivable_payable_remarks_length"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -422,6 +427,34 @@
|
|||||||
"fieldname": "round_row_wise_tax",
|
"fieldname": "round_row_wise_tax",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Round Tax Amount Row-wise"
|
"label": "Round Tax Amount Row-wise"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reports_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Reports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Truncates 'Remarks' column to set character length",
|
||||||
|
"fieldname": "general_ledger_remarks_length",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "General Ledger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Truncates 'Remarks' column to set character length",
|
||||||
|
"fieldname": "receivable_payable_remarks_length",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Accounts Receivable/Payable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_lvjk",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "remarks_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Remarks Column Length"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -429,7 +462,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-28 00:12:02.740633",
|
"modified": "2023-11-20 09:37:47.650347",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -853,6 +853,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
||||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||||
|
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"))
|
||||||
if(paid_amount > total_negative_outstanding) {
|
if(paid_amount > total_negative_outstanding) {
|
||||||
if(total_negative_outstanding == 0) {
|
if(total_negative_outstanding == 0) {
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
|
|||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
test_dependencies = ["Item"]
|
||||||
@@ -85,26 +86,44 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
|
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
|
||||||
|
|
||||||
def create_account(self):
|
def create_account(self):
|
||||||
account_name = "Debtors EUR"
|
accounts = [
|
||||||
if not frappe.db.get_value(
|
{
|
||||||
"Account", filters={"account_name": account_name, "company": self.company}
|
"attribute": "debtors_eur",
|
||||||
):
|
"account_name": "Debtors EUR",
|
||||||
acc = frappe.new_doc("Account")
|
"parent_account": "Accounts Receivable - _PR",
|
||||||
acc.account_name = account_name
|
"account_currency": "EUR",
|
||||||
acc.parent_account = "Accounts Receivable - _PR"
|
"account_type": "Receivable",
|
||||||
acc.company = self.company
|
},
|
||||||
acc.account_currency = "EUR"
|
{
|
||||||
acc.account_type = "Receivable"
|
"attribute": "creditors_usd",
|
||||||
acc.insert()
|
"account_name": "Payable USD",
|
||||||
else:
|
"parent_account": "Accounts Payable - _PR",
|
||||||
name = frappe.db.get_value(
|
"account_currency": "USD",
|
||||||
"Account",
|
"account_type": "Payable",
|
||||||
filters={"account_name": account_name, "company": self.company},
|
},
|
||||||
fieldname="name",
|
]
|
||||||
pluck=True,
|
|
||||||
)
|
for x in accounts:
|
||||||
acc = frappe.get_doc("Account", name)
|
x = frappe._dict(x)
|
||||||
self.debtors_eur = acc.name
|
if not frappe.db.get_value(
|
||||||
|
"Account", filters={"account_name": x.account_name, "company": self.company}
|
||||||
|
):
|
||||||
|
acc = frappe.new_doc("Account")
|
||||||
|
acc.account_name = x.account_name
|
||||||
|
acc.parent_account = x.parent_account
|
||||||
|
acc.company = self.company
|
||||||
|
acc.account_currency = x.account_currency
|
||||||
|
acc.account_type = x.account_type
|
||||||
|
acc.insert()
|
||||||
|
else:
|
||||||
|
name = frappe.db.get_value(
|
||||||
|
"Account",
|
||||||
|
filters={"account_name": x.account_name, "company": self.company},
|
||||||
|
fieldname="name",
|
||||||
|
pluck=True,
|
||||||
|
)
|
||||||
|
acc = frappe.get_doc("Account", name)
|
||||||
|
setattr(self, x.attribute, acc.name)
|
||||||
|
|
||||||
def create_sales_invoice(
|
def create_sales_invoice(
|
||||||
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||||
@@ -151,6 +170,64 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
payment.posting_date = posting_date
|
payment.posting_date = posting_date
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
def create_purchase_invoice(
|
||||||
|
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Helper function to populate default values in sales invoice
|
||||||
|
"""
|
||||||
|
pinv = make_purchase_invoice(
|
||||||
|
qty=qty,
|
||||||
|
rate=rate,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.supplier,
|
||||||
|
item_code=self.item,
|
||||||
|
item_name=self.item,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
update_stock=0,
|
||||||
|
currency="INR",
|
||||||
|
is_pos=0,
|
||||||
|
is_return=0,
|
||||||
|
return_against=None,
|
||||||
|
income_account=self.income_account,
|
||||||
|
expense_account=self.expense_account,
|
||||||
|
do_not_save=do_not_save,
|
||||||
|
do_not_submit=do_not_submit,
|
||||||
|
)
|
||||||
|
return pinv
|
||||||
|
|
||||||
|
def create_purchase_order(
|
||||||
|
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Helper function to populate default values in sales invoice
|
||||||
|
"""
|
||||||
|
pord = create_purchase_order(
|
||||||
|
qty=qty,
|
||||||
|
rate=rate,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.supplier,
|
||||||
|
item_code=self.item,
|
||||||
|
item_name=self.item,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
update_stock=0,
|
||||||
|
currency="INR",
|
||||||
|
is_pos=0,
|
||||||
|
is_return=0,
|
||||||
|
return_against=None,
|
||||||
|
income_account=self.income_account,
|
||||||
|
expense_account=self.expense_account,
|
||||||
|
do_not_save=do_not_save,
|
||||||
|
do_not_submit=do_not_submit,
|
||||||
|
)
|
||||||
|
return pord
|
||||||
|
|
||||||
def clear_old_entries(self):
|
def clear_old_entries(self):
|
||||||
doctype_list = [
|
doctype_list = [
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
@@ -163,13 +240,11 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
for doctype in doctype_list:
|
for doctype in doctype_list:
|
||||||
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||||
|
|
||||||
def create_payment_reconciliation(self):
|
def create_payment_reconciliation(self, party_is_customer=True):
|
||||||
pr = frappe.new_doc("Payment Reconciliation")
|
pr = frappe.new_doc("Payment Reconciliation")
|
||||||
pr.company = self.company
|
pr.company = self.company
|
||||||
pr.party_type = (
|
pr.party_type = "Customer" if party_is_customer else "Supplier"
|
||||||
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
|
pr.party = self.customer if party_is_customer else self.supplier
|
||||||
)
|
|
||||||
pr.party = self.customer
|
|
||||||
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
||||||
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||||
return pr
|
return pr
|
||||||
@@ -906,9 +981,13 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
||||||
|
|
||||||
def test_reconciliation_purchase_invoice_against_return(self):
|
def test_reconciliation_purchase_invoice_against_return(self):
|
||||||
pi = make_purchase_invoice(
|
self.supplier = "_Test Supplier USD"
|
||||||
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
|
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
|
||||||
).submit()
|
pi.supplier = self.supplier
|
||||||
|
pi.currency = "USD"
|
||||||
|
pi.conversion_rate = 50
|
||||||
|
pi.credit_to = self.creditors_usd
|
||||||
|
pi.save().submit()
|
||||||
|
|
||||||
pi_return = frappe.get_doc(pi.as_dict())
|
pi_return = frappe.get_doc(pi.as_dict())
|
||||||
pi_return.name = None
|
pi_return.name = None
|
||||||
@@ -918,11 +997,12 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
pi_return.items[0].qty = -pi_return.items[0].qty
|
pi_return.items[0].qty = -pi_return.items[0].qty
|
||||||
pi_return.submit()
|
pi_return.submit()
|
||||||
|
|
||||||
self.company = "_Test Company"
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
self.party_type = "Supplier"
|
pr.company = self.company
|
||||||
self.customer = "_Test Supplier USD"
|
pr.party_type = "Supplier"
|
||||||
|
pr.party = self.supplier
|
||||||
pr = self.create_payment_reconciliation()
|
pr.receivable_payable_account = self.creditors_usd
|
||||||
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||||
pr.get_unreconciled_entries()
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
invoices = []
|
invoices = []
|
||||||
@@ -931,6 +1011,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
if invoice.invoice_number == pi.name:
|
if invoice.invoice_number == pi.name:
|
||||||
invoices.append(invoice.as_dict())
|
invoices.append(invoice.as_dict())
|
||||||
break
|
break
|
||||||
|
|
||||||
for payment in pr.payments:
|
for payment in pr.payments:
|
||||||
if payment.reference_name == pi_return.name:
|
if payment.reference_name == pi_return.name:
|
||||||
payments.append(payment.as_dict())
|
payments.append(payment.as_dict())
|
||||||
@@ -941,6 +1022,154 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
||||||
pr.reconcile()
|
pr.reconcile()
|
||||||
|
|
||||||
|
def test_reconciliation_from_purchase_order_to_multiple_invoices(self):
|
||||||
|
"""
|
||||||
|
Reconciling advance payment from PO/SO to multiple invoices should not cause overallocation
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.supplier = "_Test Supplier"
|
||||||
|
|
||||||
|
pi1 = self.create_purchase_invoice(qty=10, rate=100)
|
||||||
|
pi2 = self.create_purchase_invoice(qty=10, rate=100)
|
||||||
|
po = self.create_purchase_order(qty=20, rate=100)
|
||||||
|
pay = get_payment_entry(po.doctype, po.name)
|
||||||
|
# Overpay Puchase Order
|
||||||
|
pay.paid_amount = 3000
|
||||||
|
pay.save().submit()
|
||||||
|
# assert total allocated and unallocated before reconciliation
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[0].reference_doctype,
|
||||||
|
pay.references[0].reference_name,
|
||||||
|
pay.references[0].allocated_amount,
|
||||||
|
),
|
||||||
|
(po.doctype, po.name, 2000),
|
||||||
|
)
|
||||||
|
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||||
|
self.assertEqual(pay.unallocated_amount, 1000)
|
||||||
|
self.assertEqual(pay.difference_amount, 0)
|
||||||
|
|
||||||
|
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
self.assertEqual(len(pr.invoices), 2)
|
||||||
|
self.assertEqual(len(pr.payments), 2)
|
||||||
|
|
||||||
|
for x in pr.payments:
|
||||||
|
self.assertEqual((x.reference_type, x.reference_name), (pay.doctype, pay.name))
|
||||||
|
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices]
|
||||||
|
payments = [x.as_dict() for x in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
# partial allocation on pi1 and full allocate on pi2
|
||||||
|
pr.allocation[0].allocated_amount = 100
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
# assert references and total allocated and unallocated amount
|
||||||
|
pay.reload()
|
||||||
|
self.assertEqual(len(pay.references), 3)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[0].reference_doctype,
|
||||||
|
pay.references[0].reference_name,
|
||||||
|
pay.references[0].allocated_amount,
|
||||||
|
),
|
||||||
|
(po.doctype, po.name, 900),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[1].reference_doctype,
|
||||||
|
pay.references[1].reference_name,
|
||||||
|
pay.references[1].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi1.doctype, pi1.name, 100),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[2].reference_doctype,
|
||||||
|
pay.references[2].reference_name,
|
||||||
|
pay.references[2].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi2.doctype, pi2.name, 1000),
|
||||||
|
)
|
||||||
|
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||||
|
self.assertEqual(pay.unallocated_amount, 1000)
|
||||||
|
self.assertEqual(pay.difference_amount, 0)
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
self.assertEqual(len(pr.invoices), 1)
|
||||||
|
self.assertEqual(len(pr.payments), 2)
|
||||||
|
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices]
|
||||||
|
payments = [x.as_dict() for x in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
# assert references and total allocated and unallocated amount
|
||||||
|
pay.reload()
|
||||||
|
self.assertEqual(len(pay.references), 3)
|
||||||
|
# PO references should be removed now
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[0].reference_doctype,
|
||||||
|
pay.references[0].reference_name,
|
||||||
|
pay.references[0].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi1.doctype, pi1.name, 100),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[1].reference_doctype,
|
||||||
|
pay.references[1].reference_name,
|
||||||
|
pay.references[1].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi2.doctype, pi2.name, 1000),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[2].reference_doctype,
|
||||||
|
pay.references[2].reference_name,
|
||||||
|
pay.references[2].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi1.doctype, pi1.name, 900),
|
||||||
|
)
|
||||||
|
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||||
|
self.assertEqual(pay.unallocated_amount, 1000)
|
||||||
|
self.assertEqual(pay.difference_amount, 0)
|
||||||
|
|
||||||
|
def test_rounding_of_unallocated_amount(self):
|
||||||
|
self.supplier = "_Test Supplier USD"
|
||||||
|
pi = self.create_purchase_invoice(qty=1, rate=10, do_not_submit=True)
|
||||||
|
pi.supplier = self.supplier
|
||||||
|
pi.currency = "USD"
|
||||||
|
pi.conversion_rate = 80
|
||||||
|
pi.credit_to = self.creditors_usd
|
||||||
|
pi.save().submit()
|
||||||
|
|
||||||
|
pe = get_payment_entry(pi.doctype, pi.name)
|
||||||
|
pe.target_exchange_rate = 78.726500000
|
||||||
|
pe.received_amount = 26.75
|
||||||
|
pe.paid_amount = 2105.93
|
||||||
|
pe.references = []
|
||||||
|
pe.save().submit()
|
||||||
|
|
||||||
|
# unallocated_amount will have some rounding loss - 26.749950
|
||||||
|
self.assertNotEqual(pe.unallocated_amount, 26.75)
|
||||||
|
|
||||||
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
|
pr.company = self.company
|
||||||
|
pr.party_type = "Supplier"
|
||||||
|
pr.party = self.supplier
|
||||||
|
pr.receivable_payable_account = self.creditors_usd
|
||||||
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||||
|
payments = [payment.as_dict() for payment in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
|
||||||
|
# Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
def make_customer(customer_name, currency=None):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"is_pos",
|
"is_pos",
|
||||||
"is_return",
|
"is_return",
|
||||||
"update_billed_amount_in_sales_order",
|
"update_billed_amount_in_sales_order",
|
||||||
|
"update_billed_amount_in_delivery_note",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
@@ -1550,12 +1551,19 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Amount Eligible for Commission",
|
"label": "Amount Eligible for Commission",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"depends_on": "eval: doc.is_return && doc.return_against",
|
||||||
|
"fieldname": "update_billed_amount_in_delivery_note",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Update Billed Amount in Delivery Note"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-03 16:23:41.083409",
|
"modified": "2023-11-20 12:27:12.848149",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
|||||||
@@ -2156,7 +2156,7 @@
|
|||||||
"label": "Use Company default Cost Center for Round off"
|
"label": "Use Company default Cost Center for Round off"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "1",
|
||||||
"depends_on": "eval: doc.is_return",
|
"depends_on": "eval: doc.is_return",
|
||||||
"fieldname": "update_billed_amount_in_delivery_note",
|
"fieldname": "update_billed_amount_in_delivery_note",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@@ -2173,7 +2173,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-11-03 14:39:38.012346",
|
"modified": "2023-11-20 11:51:43.555197",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -789,6 +789,28 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
w = self.make()
|
w = self.make()
|
||||||
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
|
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
|
||||||
|
|
||||||
|
def test_rounded_total_with_cash_discount(self):
|
||||||
|
si = frappe.copy_doc(test_records[2])
|
||||||
|
|
||||||
|
item = copy.deepcopy(si.get("items")[0])
|
||||||
|
item.update(
|
||||||
|
{
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 14960.66,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
si.set("items", [item])
|
||||||
|
si.set("taxes", [])
|
||||||
|
si.apply_discount_on = "Grand Total"
|
||||||
|
si.is_cash_or_non_trade_discount = 1
|
||||||
|
si.discount_amount = 1
|
||||||
|
si.insert()
|
||||||
|
|
||||||
|
self.assertEqual(si.grand_total, 14959.66)
|
||||||
|
self.assertEqual(si.rounded_total, 14960)
|
||||||
|
self.assertEqual(si.rounding_adjustment, 0.34)
|
||||||
|
|
||||||
def test_payment(self):
|
def test_payment(self):
|
||||||
w = self.make()
|
w = self.make()
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,12 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
||||||
from erpnext.utilities.regional import temporary_flag
|
from erpnext.utilities.regional import temporary_flag
|
||||||
|
|
||||||
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
|
PURCHASE_TRANSACTION_TYPES = {
|
||||||
|
"Supplier Quotation",
|
||||||
|
"Purchase Order",
|
||||||
|
"Purchase Receipt",
|
||||||
|
"Purchase Invoice",
|
||||||
|
}
|
||||||
SALES_TRANSACTION_TYPES = {
|
SALES_TRANSACTION_TYPES = {
|
||||||
"Quotation",
|
"Quotation",
|
||||||
"Sales Order",
|
"Sales Order",
|
||||||
@@ -231,7 +236,9 @@ def set_address_details(
|
|||||||
if shipping_address:
|
if shipping_address:
|
||||||
party_details.update(
|
party_details.update(
|
||||||
shipping_address=shipping_address,
|
shipping_address=shipping_address,
|
||||||
shipping_address_display=render_address(shipping_address),
|
shipping_address_display=render_address(
|
||||||
|
shipping_address, check_permissions=not ignore_permissions
|
||||||
|
),
|
||||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from collections import OrderedDict
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb, scrub
|
from frappe import _, qb, scrub
|
||||||
from frappe.query_builder import Criterion
|
from frappe.query_builder import Criterion
|
||||||
from frappe.query_builder.functions import Date, Sum
|
from frappe.query_builder.functions import Date, Substring, Sum
|
||||||
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
@@ -760,7 +760,12 @@ class ReceivablePayableReport(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.filters.get("show_remarks"):
|
if self.filters.get("show_remarks"):
|
||||||
query = query.select(ple.remarks)
|
if remarks_length := frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "receivable_payable_remarks_length"
|
||||||
|
):
|
||||||
|
query = query.select(Substring(ple.remarks, 1, remarks_length).as_("remarks"))
|
||||||
|
else:
|
||||||
|
query = query.select(ple.remarks)
|
||||||
|
|
||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
query = query.orderby(self.ple.party, self.ple.posting_date)
|
query = query.orderby(self.ple.party, self.ple.posting_date)
|
||||||
|
|||||||
@@ -164,7 +164,12 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
credit_in_account_currency """
|
credit_in_account_currency """
|
||||||
|
|
||||||
if filters.get("show_remarks"):
|
if filters.get("show_remarks"):
|
||||||
select_fields += """,remarks"""
|
if remarks_length := frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "general_ledger_remarks_length"
|
||||||
|
):
|
||||||
|
select_fields += f",substr(remarks, 1, {remarks_length}) as 'remarks'"
|
||||||
|
else:
|
||||||
|
select_fields += """,remarks"""
|
||||||
|
|
||||||
order_by_statement = "order by posting_date, account, creation"
|
order_by_statement = "order by posting_date, account, creation"
|
||||||
|
|
||||||
|
|||||||
@@ -648,7 +648,7 @@ def update_reference_in_payment_entry(
|
|||||||
"outstanding_amount": d.outstanding_amount,
|
"outstanding_amount": d.outstanding_amount,
|
||||||
"allocated_amount": d.allocated_amount,
|
"allocated_amount": d.allocated_amount,
|
||||||
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
||||||
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
|
"exchange_gain_loss": d.exchange_gain_loss,
|
||||||
"account": d.account,
|
"account": d.account,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,22 +661,21 @@ def update_reference_in_payment_entry(
|
|||||||
existing_row.reference_doctype, existing_row.reference_name
|
existing_row.reference_doctype, existing_row.reference_name
|
||||||
).set_total_advance_paid()
|
).set_total_advance_paid()
|
||||||
|
|
||||||
original_row = existing_row.as_dict().copy()
|
if d.allocated_amount <= existing_row.allocated_amount:
|
||||||
existing_row.update(reference_details)
|
existing_row.allocated_amount -= d.allocated_amount
|
||||||
|
|
||||||
if d.allocated_amount < original_row.allocated_amount:
|
|
||||||
new_row = payment_entry.append("references")
|
new_row = payment_entry.append("references")
|
||||||
new_row.docstatus = 1
|
new_row.docstatus = 1
|
||||||
for field in list(reference_details):
|
for field in list(reference_details):
|
||||||
new_row.set(field, original_row[field])
|
new_row.set(field, reference_details[field])
|
||||||
|
|
||||||
new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount
|
|
||||||
else:
|
else:
|
||||||
new_row = payment_entry.append("references")
|
new_row = payment_entry.append("references")
|
||||||
new_row.docstatus = 1
|
new_row.docstatus = 1
|
||||||
new_row.update(reference_details)
|
new_row.update(reference_details)
|
||||||
|
|
||||||
payment_entry.flags.ignore_validate_update_after_submit = True
|
payment_entry.flags.ignore_validate_update_after_submit = True
|
||||||
|
payment_entry.clear_unallocated_reference_document_rows()
|
||||||
payment_entry.setup_party_account_field()
|
payment_entry.setup_party_account_field()
|
||||||
payment_entry.set_missing_values()
|
payment_entry.set_missing_values()
|
||||||
if not skip_ref_details_update_for_pe:
|
if not skip_ref_details_update_for_pe:
|
||||||
|
|||||||
@@ -481,6 +481,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "1",
|
||||||
"fieldname": "asset_quantity",
|
"fieldname": "asset_quantity",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Asset Quantity",
|
"label": "Asset Quantity",
|
||||||
@@ -571,7 +572,7 @@
|
|||||||
"link_fieldname": "target_asset"
|
"link_fieldname": "target_asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-11-15 17:40:17.315203",
|
"modified": "2023-11-20 20:57:37.010467",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@@ -46,12 +46,28 @@ class Asset(AccountsController):
|
|||||||
self.validate_item()
|
self.validate_item()
|
||||||
self.validate_cost_center()
|
self.validate_cost_center()
|
||||||
self.set_missing_values()
|
self.set_missing_values()
|
||||||
self.validate_finance_books()
|
|
||||||
if not self.split_from:
|
|
||||||
self.prepare_depreciation_data()
|
|
||||||
update_draft_asset_depr_schedules(self)
|
|
||||||
self.validate_gross_and_purchase_amount()
|
self.validate_gross_and_purchase_amount()
|
||||||
self.validate_expected_value_after_useful_life()
|
self.validate_expected_value_after_useful_life()
|
||||||
|
self.validate_finance_books()
|
||||||
|
|
||||||
|
if not self.split_from:
|
||||||
|
self.prepare_depreciation_data()
|
||||||
|
|
||||||
|
if self.calculate_depreciation:
|
||||||
|
update_draft_asset_depr_schedules(self)
|
||||||
|
|
||||||
|
if frappe.db.exists("Asset", self.name):
|
||||||
|
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
|
||||||
|
|
||||||
|
if asset_depr_schedules_names:
|
||||||
|
asset_depr_schedules_links = get_comma_separated_links(
|
||||||
|
asset_depr_schedules_names, "Asset Depreciation Schedule"
|
||||||
|
)
|
||||||
|
frappe.msgprint(
|
||||||
|
_(
|
||||||
|
"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
||||||
|
).format(asset_depr_schedules_links)
|
||||||
|
)
|
||||||
|
|
||||||
self.status = self.get_status()
|
self.status = self.get_status()
|
||||||
|
|
||||||
@@ -61,17 +77,7 @@ class Asset(AccountsController):
|
|||||||
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
if self.calculate_depreciation and not self.split_from:
|
if self.calculate_depreciation and not self.split_from:
|
||||||
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
|
|
||||||
convert_draft_asset_depr_schedules_into_active(self)
|
convert_draft_asset_depr_schedules_into_active(self)
|
||||||
if asset_depr_schedules_names:
|
|
||||||
asset_depr_schedules_links = get_comma_separated_links(
|
|
||||||
asset_depr_schedules_names, "Asset Depreciation Schedule"
|
|
||||||
)
|
|
||||||
frappe.msgprint(
|
|
||||||
_(
|
|
||||||
"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
|
||||||
).format(asset_depr_schedules_links)
|
|
||||||
)
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
add_asset_activity(self.name, _("Asset submitted"))
|
add_asset_activity(self.name, _("Asset submitted"))
|
||||||
|
|
||||||
@@ -823,6 +829,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount):
|
|||||||
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
||||||
* flt(d.salvage_value_percentage / 100),
|
* flt(d.salvage_value_percentage / 100),
|
||||||
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||||
|
"rate_of_depreciation": d.rate_of_depreciation,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"company",
|
"company",
|
||||||
|
"billing_address",
|
||||||
|
"billing_address_display",
|
||||||
"vendor",
|
"vendor",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
@@ -292,13 +294,25 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Send Document Print",
|
"label": "Send Document Print",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company Billing Address",
|
||||||
|
"options": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address_display",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Billing Address Details",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-09 12:20:26.850623",
|
"modified": "2023-11-06 12:45:28.898706",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation",
|
"name": "Request for Quotation",
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
"valid_till",
|
"valid_till",
|
||||||
"quotation_number",
|
"quotation_number",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
|
"accounting_dimensions_section",
|
||||||
|
"cost_center",
|
||||||
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"currency_and_price_list",
|
"currency_and_price_list",
|
||||||
"currency",
|
"currency",
|
||||||
"conversion_rate",
|
"conversion_rate",
|
||||||
@@ -79,6 +83,7 @@
|
|||||||
"pricing_rule_details",
|
"pricing_rule_details",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"address_and_contact_tab",
|
"address_and_contact_tab",
|
||||||
|
"supplier_address_section",
|
||||||
"supplier_address",
|
"supplier_address",
|
||||||
"address_display",
|
"address_display",
|
||||||
"column_break_72",
|
"column_break_72",
|
||||||
@@ -86,6 +91,14 @@
|
|||||||
"contact_display",
|
"contact_display",
|
||||||
"contact_mobile",
|
"contact_mobile",
|
||||||
"contact_email",
|
"contact_email",
|
||||||
|
"shipping_address_section",
|
||||||
|
"shipping_address",
|
||||||
|
"column_break_zjaq",
|
||||||
|
"shipping_address_display",
|
||||||
|
"company_billing_address_section",
|
||||||
|
"billing_address",
|
||||||
|
"column_break_gcth",
|
||||||
|
"billing_address_display",
|
||||||
"terms_tab",
|
"terms_tab",
|
||||||
"tc_name",
|
"tc_name",
|
||||||
"terms",
|
"terms",
|
||||||
@@ -838,6 +851,76 @@
|
|||||||
"fieldname": "named_place",
|
"fieldname": "named_place",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Named Place"
|
"label": "Named Place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "shipping_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Shipping Address",
|
||||||
|
"options": "Address",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_zjaq",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "shipping_address_display",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Shipping Address Details",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "shipping_address_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Shipping Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_address_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Supplier Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_billing_address_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Company Billing Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company Billing Address",
|
||||||
|
"options": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_gcth",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address_display",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Billing Address Details",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_col_break",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimensions_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
@@ -845,7 +928,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-03 16:20:15.880114",
|
"modified": "2023-11-20 11:15:30.083077",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation",
|
"name": "Supplier Quotation",
|
||||||
|
|||||||
@@ -68,6 +68,8 @@
|
|||||||
"column_break_15",
|
"column_break_15",
|
||||||
"manufacturer_part_no",
|
"manufacturer_part_no",
|
||||||
"ad_sec_break",
|
"ad_sec_break",
|
||||||
|
"cost_center",
|
||||||
|
"dimension_col_break",
|
||||||
"project",
|
"project",
|
||||||
"section_break_44",
|
"section_break_44",
|
||||||
"page_break"
|
"page_break"
|
||||||
@@ -133,7 +135,6 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.image",
|
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@@ -554,13 +555,23 @@
|
|||||||
"fieldname": "expected_delivery_date",
|
"fieldname": "expected_delivery_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Expected Delivery Date"
|
"label": "Expected Delivery Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_col_break",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-14 18:35:03.435817",
|
"modified": "2023-11-17 12:25:26.235367",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation Item",
|
"name": "Supplier Quotation Item",
|
||||||
|
|||||||
@@ -758,7 +758,7 @@ class BuyingController(SubcontractingController):
|
|||||||
"calculate_depreciation": 0,
|
"calculate_depreciation": 0,
|
||||||
"purchase_receipt_amount": purchase_amount,
|
"purchase_receipt_amount": purchase_amount,
|
||||||
"gross_purchase_amount": purchase_amount,
|
"gross_purchase_amount": purchase_amount,
|
||||||
"asset_quantity": row.qty if is_grouped_asset else 0,
|
"asset_quantity": row.qty if is_grouped_asset else 1,
|
||||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,6 +356,7 @@ def make_return_doc(
|
|||||||
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
|
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
|
||||||
doc.consolidated_invoice = ""
|
doc.consolidated_invoice = ""
|
||||||
doc.set("payments", [])
|
doc.set("payments", [])
|
||||||
|
doc.update_billed_amount_in_delivery_note = True
|
||||||
for data in source.payments:
|
for data in source.payments:
|
||||||
paid_amount = 0.00
|
paid_amount = 0.00
|
||||||
base_paid_amount = 0.00
|
base_paid_amount = 0.00
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||||
self.doc.grand_total -= self.doc.discount_amount
|
self.doc.grand_total -= self.doc.discount_amount
|
||||||
self.doc.base_grand_total -= self.doc.base_discount_amount
|
self.doc.base_grand_total -= self.doc.base_discount_amount
|
||||||
|
self.doc.rounding_adjustment = self.doc.base_rounding_adjustment = 0.0
|
||||||
self.set_rounded_total()
|
self.set_rounded_total()
|
||||||
|
|
||||||
self.calculate_shipping_charges()
|
self.calculate_shipping_charges()
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ scheduler_events = {
|
|||||||
"hourly_long": [
|
"hourly_long": [
|
||||||
"erpnext.accounts.doctype.process_subscription.process_subscription.create_subscription_process",
|
"erpnext.accounts.doctype.process_subscription.process_subscription.create_subscription_process",
|
||||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
||||||
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
"erpnext.utilities.bulk_transaction.retry",
|
||||||
],
|
],
|
||||||
"daily": [
|
"daily": [
|
||||||
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
||||||
@@ -539,6 +539,8 @@ accounting_dimension_doctypes = [
|
|||||||
"Subcontracting Receipt",
|
"Subcontracting Receipt",
|
||||||
"Subcontracting Receipt Item",
|
"Subcontracting Receipt Item",
|
||||||
"Account Closing Balance",
|
"Account Closing Balance",
|
||||||
|
"Supplier Quotation",
|
||||||
|
"Supplier Quotation Item",
|
||||||
]
|
]
|
||||||
|
|
||||||
get_matching_queries = (
|
get_matching_queries = (
|
||||||
|
|||||||
@@ -349,5 +349,7 @@ erpnext.patches.v14_0.add_default_for_repost_settings
|
|||||||
erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month
|
erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month
|
||||||
erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based
|
erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based
|
||||||
erpnext.patches.v15_0.set_reserved_stock_in_bin
|
erpnext.patches.v15_0.set_reserved_stock_in_bin
|
||||||
|
erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
|
||||||
|
erpnext.patches.v14_0.update_zero_asset_quantity_field
|
||||||
# below migration patch should always run last
|
# below migration patch should always run last
|
||||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
|
create_accounting_dimensions_for_doctype,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
create_accounting_dimensions_for_doctype(doctype="Supplier Quotation")
|
||||||
|
create_accounting_dimensions_for_doctype(doctype="Supplier Quotation Item")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
asset = frappe.qb.DocType("Asset")
|
||||||
|
frappe.qb.update(asset).set(asset.asset_quantity, 1).where(asset.asset_quantity == 0).run()
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_in_quick_entry": 1,
|
||||||
"fieldname": "subject",
|
"fieldname": "subject",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_in_quick_entry": 1,
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -396,7 +398,7 @@
|
|||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 5,
|
"max_attachments": 5,
|
||||||
"modified": "2023-09-28 13:52:05.861175",
|
"modified": "2023-11-20 11:42:41.884069",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Task",
|
"name": "Task",
|
||||||
@@ -416,6 +418,7 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
"search_fields": "subject",
|
"search_fields": "subject",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"show_preview_popup": 1,
|
"show_preview_popup": 1,
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ frappe.ui.form.on("Timesheet", {
|
|||||||
|
|
||||||
frm.trigger('setup_filters');
|
frm.trigger('setup_filters');
|
||||||
frm.trigger('set_dynamic_field_label');
|
frm.trigger('set_dynamic_field_label');
|
||||||
|
frm.trigger('set_route_options_for_new_task');
|
||||||
},
|
},
|
||||||
|
|
||||||
customer: function(frm) {
|
customer: function(frm) {
|
||||||
@@ -172,6 +173,14 @@ frappe.ui.form.on("Timesheet", {
|
|||||||
frm.refresh_fields();
|
frm.refresh_fields();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
set_route_options_for_new_task: (frm) => {
|
||||||
|
let task_field = frm.get_docfield('time_logs', 'task');
|
||||||
|
|
||||||
|
if (task_field) {
|
||||||
|
task_field.get_route_options_for_new_doc = (row) => ({'project': row.doc.project});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
make_invoice: function(frm) {
|
make_invoice: function(frm) {
|
||||||
let fields = [{
|
let fields = [{
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ class Timesheet(Document):
|
|||||||
if args.is_billable:
|
if args.is_billable:
|
||||||
if flt(args.billing_hours) == 0.0:
|
if flt(args.billing_hours) == 0.0:
|
||||||
args.billing_hours = args.hours
|
args.billing_hours = args.hours
|
||||||
|
elif flt(args.billing_hours) > flt(args.hours):
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx),
|
||||||
|
indicator="orange",
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
args.billing_hours = 0
|
args.billing_hours = 0
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) {
|
if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) {
|
||||||
this.frm.doc.grand_total -= this.frm.doc.discount_amount;
|
this.frm.doc.grand_total -= this.frm.doc.discount_amount;
|
||||||
this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount;
|
this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount;
|
||||||
|
this.frm.doc.rounding_adjustment = 0;
|
||||||
|
this.frm.doc.base_rounding_adjustment = 0;
|
||||||
|
this.set_rounded_total();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.calculate_shipping_charges();
|
await this.calculate_shipping_charges();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
frappe.provide("erpnext.utils");
|
frappe.provide("erpnext.utils");
|
||||||
|
|
||||||
const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
|
const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
|
||||||
const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
|
const PURCHASE_DOCTYPES = ['Supplier Quotation','Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
|
||||||
|
|
||||||
erpnext.utils.get_party_details = function(frm, method, args, callback) {
|
erpnext.utils.get_party_details = function(frm, method, args, callback) {
|
||||||
if (!method) {
|
if (!method) {
|
||||||
|
|||||||
@@ -644,8 +644,12 @@ def make_contact(args, is_primary_contact=1):
|
|||||||
"is_primary_contact": is_primary_contact,
|
"is_primary_contact": is_primary_contact,
|
||||||
"links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}],
|
"links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}],
|
||||||
}
|
}
|
||||||
if args.customer_type == "Individual":
|
|
||||||
first, middle, last = parse_full_name(args.get("customer_name"))
|
party_type = args.customer_type if args.doctype == "Customer" else args.supplier_type
|
||||||
|
party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name"
|
||||||
|
|
||||||
|
if party_type == "Individual":
|
||||||
|
first, middle, last = parse_full_name(args.get(party_name_key))
|
||||||
values.update(
|
values.update(
|
||||||
{
|
{
|
||||||
"first_name": first,
|
"first_name": first,
|
||||||
@@ -656,9 +660,10 @@ def make_contact(args, is_primary_contact=1):
|
|||||||
else:
|
else:
|
||||||
values.update(
|
values.update(
|
||||||
{
|
{
|
||||||
"company_name": args.get("customer_name"),
|
"company_name": args.get(party_name_key),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
contact = frappe.get_doc(values)
|
contact = frappe.get_doc(values)
|
||||||
|
|
||||||
if args.get("email_id"):
|
if args.get("email_id"):
|
||||||
@@ -687,10 +692,12 @@ def make_address(args, is_primary_address=1, is_shipping_address=1):
|
|||||||
title=_("Missing Values Required"),
|
title=_("Missing Values Required"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name"
|
||||||
|
|
||||||
address = frappe.get_doc(
|
address = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Address",
|
"doctype": "Address",
|
||||||
"address_title": args.get("customer_name"),
|
"address_title": args.get(party_name_key),
|
||||||
"address_line1": args.get("address_line1"),
|
"address_line1": args.get("address_line1"),
|
||||||
"address_line2": args.get("address_line2"),
|
"address_line2": args.get("address_line2"),
|
||||||
"city": args.get("city"),
|
"city": args.get("city"),
|
||||||
|
|||||||
@@ -1784,10 +1784,10 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
si.submit()
|
si.submit()
|
||||||
pe.load_from_db()
|
pe.load_from_db()
|
||||||
|
|
||||||
self.assertEqual(pe.references[0].reference_name, si.name)
|
self.assertEqual(pe.references[0].reference_name, so.name)
|
||||||
self.assertEqual(pe.references[0].allocated_amount, 200)
|
self.assertEqual(pe.references[0].allocated_amount, 300)
|
||||||
self.assertEqual(pe.references[1].reference_name, so.name)
|
self.assertEqual(pe.references[1].reference_name, si.name)
|
||||||
self.assertEqual(pe.references[1].allocated_amount, 300)
|
self.assertEqual(pe.references[1].allocated_amount, 200)
|
||||||
|
|
||||||
def test_delivered_item_material_request(self):
|
def test_delivered_item_material_request(self):
|
||||||
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
||||||
|
|||||||
@@ -504,7 +504,7 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Variant Attributes",
|
"label": "Variant Attributes",
|
||||||
"mandatory_depends_on": "has_variants",
|
"mandatory_depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'",
|
||||||
"options": "Item Variant Attribute"
|
"options": "Item Variant Attribute"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -888,7 +888,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2023-09-11 13:46:32.688051",
|
"modified": "2023-09-18 15:41:32.688051",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
|
|||||||
@@ -731,12 +731,18 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
def update_assets(self, item, valuation_rate):
|
def update_assets(self, item, valuation_rate):
|
||||||
assets = frappe.db.get_all(
|
assets = frappe.db.get_all(
|
||||||
"Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code}
|
"Asset",
|
||||||
|
filters={"purchase_receipt": self.name, "item_code": item.item_code},
|
||||||
|
fields=["name", "asset_quantity"],
|
||||||
)
|
)
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(valuation_rate))
|
frappe.db.set_value(
|
||||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate))
|
"Asset", asset.name, "gross_purchase_amount", flt(valuation_rate) * asset.asset_quantity
|
||||||
|
)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate) * asset.asset_quantity
|
||||||
|
)
|
||||||
|
|
||||||
def update_status(self, status):
|
def update_status(self, status):
|
||||||
self.set_status(update=True, status=status)
|
self.set_status(update=True, status=status)
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ class SerialandBatchBundle(Document):
|
|||||||
valuation_field = "rate"
|
valuation_field = "rate"
|
||||||
child_table = "Subcontracting Receipt Supplied Item"
|
child_table = "Subcontracting Receipt Supplied Item"
|
||||||
else:
|
else:
|
||||||
valuation_field = "rm_supp_cost"
|
valuation_field = "rate"
|
||||||
child_table = "Subcontracting Receipt Item"
|
child_table = "Subcontracting Receipt Item"
|
||||||
|
|
||||||
precision = frappe.get_precision(child_table, valuation_field) or 2
|
precision = frappe.get_precision(child_table, valuation_field) or 2
|
||||||
@@ -401,7 +401,7 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01:
|
if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01:
|
||||||
self.throw_error_message(
|
self.throw_error_message(
|
||||||
f"Total quantity {abs(self.total_qty)} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(row.get(qty_field))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
|
f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_is_outward(self):
|
def set_is_outward(self):
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ def get_valuation_rate():
|
|||||||
bin_data = (
|
bin_data = (
|
||||||
frappe.qb.from_(bin)
|
frappe.qb.from_(bin)
|
||||||
.select(
|
.select(
|
||||||
bin.item_code, Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty).as_("val_rate")
|
bin.item_code, (Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty)).as_("val_rate")
|
||||||
)
|
)
|
||||||
.where(bin.actual_qty > 0)
|
.where(bin.actual_qty > 0)
|
||||||
.groupby(bin.item_code)
|
.groupby(bin.item_code)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"supplier",
|
"supplier",
|
||||||
"supplier_name",
|
"supplier_name",
|
||||||
|
"supplier_delivery_note",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
@@ -634,12 +635,17 @@
|
|||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Get Scrap Items",
|
"label": "Get Scrap Items",
|
||||||
"options": "get_scrap_items"
|
"options": "get_scrap_items"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_delivery_note",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Supplier Delivery Note"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-26 10:52:04.050829",
|
"modified": "2023-11-16 13:04:00.710534",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt",
|
"name": "Subcontracting Receipt",
|
||||||
|
|||||||
@@ -667,6 +667,104 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
|
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_subcontracting_receipt_valuation_for_fg_with_auto_created_serial_batch_bundle(self):
|
||||||
|
set_backflush_based_on("BOM")
|
||||||
|
|
||||||
|
fg_item = make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"is_sub_contracted_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BSSNGS-.####",
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
rm_item1 = make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BNGS-.####",
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
rm_item2 = make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BNGS-.####",
|
||||||
|
"serial_no_series": "BNSS-.####",
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
rm_item3 = make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "BSSSS-.####",
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3])
|
||||||
|
|
||||||
|
rm_batch_no = None
|
||||||
|
for row in bom.items:
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=row.item_code,
|
||||||
|
qty=1,
|
||||||
|
target="_Test Warehouse 1 - _TC",
|
||||||
|
rate=300,
|
||||||
|
)
|
||||||
|
|
||||||
|
service_items = [
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item_code": "Subcontracted Service Item 1",
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 100,
|
||||||
|
"fg_item": fg_item,
|
||||||
|
"fg_item_qty": 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
sco = get_subcontracting_order(service_items=service_items)
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1
|
||||||
|
)
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
scr.save()
|
||||||
|
scr.submit()
|
||||||
|
scr.reload()
|
||||||
|
|
||||||
|
for row in scr.supplied_items:
|
||||||
|
self.assertEqual(row.rate, 300.00)
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
auto_created_serial_batch = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_no": scr.name, "voucher_detail_no": row.name},
|
||||||
|
"auto_created_serial_and_batch_bundle",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(auto_created_serial_batch)
|
||||||
|
|
||||||
|
self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
|
||||||
|
self.assertEqual(scr.items[0].service_cost_per_qty, 100)
|
||||||
|
self.assertEqual(scr.items[0].rate, 1000)
|
||||||
|
valuation_rate = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_no": scr.name, "voucher_detail_no": scr.items[0].name},
|
||||||
|
"valuation_rate",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(flt(valuation_rate), flt(1000))
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
|
||||||
|
)
|
||||||
|
|
||||||
def test_subcontracting_receipt_raw_material_rate(self):
|
def test_subcontracting_receipt_raw_material_rate(self):
|
||||||
# Step - 1: Set Backflush Based On as "BOM"
|
# Step - 1: Set Backflush Based On as "BOM"
|
||||||
set_backflush_based_on("BOM")
|
set_backflush_based_on("BOM")
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ def transaction_processing(data, from_doctype, to_doctype):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def retry(date: str | None):
|
def retry(date: str | None = None):
|
||||||
|
if not date:
|
||||||
|
date = today()
|
||||||
|
|
||||||
if date:
|
if date:
|
||||||
failed_docs = frappe.db.get_all(
|
failed_docs = frappe.db.get_all(
|
||||||
"Bulk Transaction Log Detail",
|
"Bulk Transaction Log Detail",
|
||||||
|
|||||||
Reference in New Issue
Block a user