mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-07 11:43:04 +00:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42ccc092fe | ||
|
|
222bce62bf | ||
|
|
487aa35ee3 | ||
|
|
0f848fd968 | ||
|
|
c117ab851a | ||
|
|
b7b62a8966 | ||
|
|
86cf156968 | ||
|
|
25718d9f1b | ||
|
|
9b66a06c86 | ||
|
|
7c2bd24d0b | ||
|
|
af57cc6c5f | ||
|
|
25448fcdbc | ||
|
|
69e83ff6ab | ||
|
|
87a8c8d08e | ||
|
|
4c01128827 | ||
|
|
712ddb75be | ||
|
|
78a9edf6c9 | ||
|
|
20ca948e6b | ||
|
|
f1407bcfd2 | ||
|
|
b605b08ec1 | ||
|
|
fabcfc1fce | ||
|
|
cfb9d8c6be | ||
|
|
6a6a5b0a75 | ||
|
|
3f296eea3a | ||
|
|
8e4e4a9fb4 | ||
|
|
254dd3d9bf | ||
|
|
83b3785202 | ||
|
|
b017f4a817 | ||
|
|
52cfe3f612 | ||
|
|
fd21dcd3b5 | ||
|
|
fea27d5e2e | ||
|
|
848928e7d1 | ||
|
|
0f4e50185e | ||
|
|
a0893ddf96 | ||
|
|
556095daaa | ||
|
|
375be8cd93 | ||
|
|
38665760cd | ||
|
|
f5120a6dd0 | ||
|
|
519bf3d377 | ||
|
|
cef2231d6a | ||
|
|
e457d39b5d | ||
|
|
7558b622a4 | ||
|
|
c58fefb359 | ||
|
|
e96d5b314a | ||
|
|
a615535ca5 | ||
|
|
fa490ef2f0 | ||
|
|
e01e9ebc37 | ||
|
|
89f07dcfac | ||
|
|
d02ebd6215 | ||
|
|
f5e9913746 | ||
|
|
a7b75a4cc1 | ||
|
|
6b951bd17a | ||
|
|
912c315286 | ||
|
|
f35a0c227d | ||
|
|
2317f6a000 | ||
|
|
91130854d8 | ||
|
|
677d728e67 | ||
|
|
fa5780ca81 | ||
|
|
80774e2da1 | ||
|
|
72d32a4901 | ||
|
|
638c271d70 | ||
|
|
356b1bdb38 | ||
|
|
0b082f0edc | ||
|
|
be90be37c7 | ||
|
|
922ace4076 | ||
|
|
920de792ce | ||
|
|
d576fc7490 | ||
|
|
7b9daeff66 | ||
|
|
c921d7d409 | ||
|
|
c8922ad566 | ||
|
|
d71b885fb8 | ||
|
|
f413530493 | ||
|
|
ae788e8358 | ||
|
|
515bed8c80 | ||
|
|
063d658b04 | ||
|
|
fa7fa85d92 | ||
|
|
ca13816ca1 | ||
|
|
78b7c26420 | ||
|
|
8e31379ecc | ||
|
|
c05e0a4ffc | ||
|
|
ec208b8df5 | ||
|
|
1cb9f4cf8b | ||
|
|
50daf701dd | ||
|
|
8660faaa54 |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.44.1"
|
||||
__version__ = "14.46.1"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -17,6 +17,7 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s
|
||||
get_entries,
|
||||
)
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
|
||||
class BankReconciliationTool(Document):
|
||||
@@ -129,7 +130,7 @@ def create_journal_entry_bts(
|
||||
bank_transaction = frappe.db.get_values(
|
||||
"Bank Transaction",
|
||||
bank_transaction_name,
|
||||
fieldname=["name", "deposit", "withdrawal", "bank_account"],
|
||||
fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"],
|
||||
as_dict=True,
|
||||
)[0]
|
||||
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
|
||||
@@ -143,29 +144,94 @@ def create_journal_entry_bts(
|
||||
)
|
||||
|
||||
company = frappe.get_value("Account", company_account, "company")
|
||||
company_default_currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||
company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency")
|
||||
second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency")
|
||||
|
||||
# determine if multi-currency Journal or not
|
||||
is_multi_currency = (
|
||||
True
|
||||
if company_default_currency != company_account_currency
|
||||
or company_default_currency != second_account_currency
|
||||
or company_default_currency != bank_transaction.currency
|
||||
else False
|
||||
)
|
||||
|
||||
accounts = []
|
||||
# Multi Currency?
|
||||
accounts.append(
|
||||
{
|
||||
"account": second_account,
|
||||
"credit_in_account_currency": bank_transaction.deposit,
|
||||
"debit_in_account_currency": bank_transaction.withdrawal,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
)
|
||||
second_account_dict = {
|
||||
"account": second_account,
|
||||
"account_currency": second_account_currency,
|
||||
"credit_in_account_currency": bank_transaction.deposit,
|
||||
"debit_in_account_currency": bank_transaction.withdrawal,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
|
||||
accounts.append(
|
||||
{
|
||||
"account": company_account,
|
||||
"bank_account": bank_transaction.bank_account,
|
||||
"credit_in_account_currency": bank_transaction.withdrawal,
|
||||
"debit_in_account_currency": bank_transaction.deposit,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
)
|
||||
company_account_dict = {
|
||||
"account": company_account,
|
||||
"account_currency": company_account_currency,
|
||||
"bank_account": bank_transaction.bank_account,
|
||||
"credit_in_account_currency": bank_transaction.withdrawal,
|
||||
"debit_in_account_currency": bank_transaction.deposit,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
|
||||
# convert transaction amount to company currency
|
||||
if is_multi_currency:
|
||||
exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date)
|
||||
withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal))
|
||||
deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit))
|
||||
else:
|
||||
withdrawal_in_company_currency = bank_transaction.withdrawal
|
||||
deposit_in_company_currency = bank_transaction.deposit
|
||||
|
||||
# if second account is of foreign currency, convert and set debit and credit fields.
|
||||
if second_account_currency != company_default_currency:
|
||||
exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date)
|
||||
second_account_dict.update(
|
||||
{
|
||||
"exchange_rate": exc_rate,
|
||||
"credit": deposit_in_company_currency,
|
||||
"debit": withdrawal_in_company_currency,
|
||||
"credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0,
|
||||
"debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0,
|
||||
}
|
||||
)
|
||||
else:
|
||||
second_account_dict.update(
|
||||
{
|
||||
"exchange_rate": 1,
|
||||
"credit": deposit_in_company_currency,
|
||||
"debit": withdrawal_in_company_currency,
|
||||
"credit_in_account_currency": deposit_in_company_currency,
|
||||
"debit_in_account_currency": withdrawal_in_company_currency,
|
||||
}
|
||||
)
|
||||
|
||||
# if company account is of foreign currency, convert and set debit and credit fields.
|
||||
if company_account_currency != company_default_currency:
|
||||
exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date)
|
||||
company_account_dict.update(
|
||||
{
|
||||
"exchange_rate": exc_rate,
|
||||
"credit": withdrawal_in_company_currency,
|
||||
"debit": deposit_in_company_currency,
|
||||
}
|
||||
)
|
||||
else:
|
||||
company_account_dict.update(
|
||||
{
|
||||
"exchange_rate": 1,
|
||||
"credit": withdrawal_in_company_currency,
|
||||
"debit": deposit_in_company_currency,
|
||||
"credit_in_account_currency": withdrawal_in_company_currency,
|
||||
"debit_in_account_currency": deposit_in_company_currency,
|
||||
}
|
||||
)
|
||||
|
||||
accounts.append(second_account_dict)
|
||||
accounts.append(company_account_dict)
|
||||
|
||||
journal_entry_dict = {
|
||||
"voucher_type": entry_type,
|
||||
@@ -175,6 +241,9 @@ def create_journal_entry_bts(
|
||||
"cheque_no": reference_number,
|
||||
"mode_of_payment": mode_of_payment,
|
||||
}
|
||||
if is_multi_currency:
|
||||
journal_entry_dict.update({"multi_currency": True})
|
||||
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.update(journal_entry_dict)
|
||||
journal_entry.set("accounts", accounts)
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Bank Statement Import", {
|
||||
onload(frm) {
|
||||
frm.set_query("bank_account", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setup(frm) {
|
||||
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
|
||||
frm.import_in_progress = false;
|
||||
|
||||
@@ -152,6 +152,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
frm.events.show_general_ledger(frm);
|
||||
if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) {
|
||||
frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() {
|
||||
frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
|
||||
}, __('Actions'));
|
||||
|
||||
}
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
},
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Posting Date"
|
||||
"label": "Posting Date",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "account_type",
|
||||
@@ -147,7 +148,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-08-22 15:32:56.629430",
|
||||
"modified": "2023-10-30 16:15:00.470283",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Ledger Entry",
|
||||
|
||||
@@ -216,6 +216,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
this.data = [];
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Difference Account"),
|
||||
size: 'extra-large',
|
||||
fields: [
|
||||
{
|
||||
fieldname: "allocation",
|
||||
@@ -239,6 +240,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
in_list_view: 1,
|
||||
read_only: 1
|
||||
}, {
|
||||
fieldtype:'Date',
|
||||
fieldname:"gain_loss_posting_date",
|
||||
label: __("Posting Date"),
|
||||
in_list_view: 1,
|
||||
reqd: 1,
|
||||
}, {
|
||||
|
||||
fieldtype:'Link',
|
||||
options: 'Account',
|
||||
in_list_view: 1,
|
||||
@@ -272,6 +280,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
args.forEach(d => {
|
||||
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||
"difference_account", d.difference_account);
|
||||
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||
"gain_loss_posting_date", d.gain_loss_posting_date);
|
||||
|
||||
});
|
||||
|
||||
this.reconcile_payment_entries();
|
||||
@@ -287,6 +298,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
'reference_name': d.reference_name,
|
||||
'difference_amount': d.difference_amount,
|
||||
'difference_account': d.difference_account,
|
||||
'gain_loss_posting_date': d.gain_loss_posting_date
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -315,6 +315,7 @@ class PaymentReconciliation(Document):
|
||||
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
||||
res.difference_account = default_exchange_gain_loss_account
|
||||
res.exchange_rate = inv.get("exchange_rate")
|
||||
res.update({"gain_loss_posting_date": pay.get("posting_date")})
|
||||
|
||||
if pay.get("amount") == 0:
|
||||
entries.append(res)
|
||||
@@ -421,6 +422,7 @@ class PaymentReconciliation(Document):
|
||||
"allocated_amount": flt(row.get("allocated_amount")),
|
||||
"difference_amount": flt(row.get("difference_amount")),
|
||||
"difference_account": row.get("difference_account"),
|
||||
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||
"cost_center": row.get("cost_center"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
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
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
@@ -85,26 +86,44 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
|
||||
|
||||
def create_account(self):
|
||||
account_name = "Debtors EUR"
|
||||
if not frappe.db.get_value(
|
||||
"Account", filters={"account_name": account_name, "company": self.company}
|
||||
):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = account_name
|
||||
acc.parent_account = "Accounts Receivable - _PR"
|
||||
acc.company = self.company
|
||||
acc.account_currency = "EUR"
|
||||
acc.account_type = "Receivable"
|
||||
acc.insert()
|
||||
else:
|
||||
name = frappe.db.get_value(
|
||||
"Account",
|
||||
filters={"account_name": account_name, "company": self.company},
|
||||
fieldname="name",
|
||||
pluck=True,
|
||||
)
|
||||
acc = frappe.get_doc("Account", name)
|
||||
self.debtors_eur = acc.name
|
||||
accounts = [
|
||||
{
|
||||
"attribute": "debtors_eur",
|
||||
"account_name": "Debtors EUR",
|
||||
"parent_account": "Accounts Receivable - _PR",
|
||||
"account_currency": "EUR",
|
||||
"account_type": "Receivable",
|
||||
},
|
||||
{
|
||||
"attribute": "creditors_usd",
|
||||
"account_name": "Payable USD",
|
||||
"parent_account": "Accounts Payable - _PR",
|
||||
"account_currency": "USD",
|
||||
"account_type": "Payable",
|
||||
},
|
||||
]
|
||||
|
||||
for x in accounts:
|
||||
x = frappe._dict(x)
|
||||
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(
|
||||
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
|
||||
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):
|
||||
doctype_list = [
|
||||
"GL Entry",
|
||||
@@ -163,13 +240,11 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
for doctype in doctype_list:
|
||||
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.company = self.company
|
||||
pr.party_type = (
|
||||
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
|
||||
)
|
||||
pr.party = self.customer
|
||||
pr.party_type = "Customer" if party_is_customer else "Supplier"
|
||||
pr.party = self.customer if party_is_customer else self.supplier
|
||||
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()
|
||||
return pr
|
||||
@@ -906,9 +981,13 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
||||
|
||||
def test_reconciliation_purchase_invoice_against_return(self):
|
||||
pi = make_purchase_invoice(
|
||||
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
|
||||
).submit()
|
||||
self.supplier = "_Test Supplier USD"
|
||||
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
|
||||
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.name = None
|
||||
@@ -918,11 +997,12 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
pi_return.items[0].qty = -pi_return.items[0].qty
|
||||
pi_return.submit()
|
||||
|
||||
self.company = "_Test Company"
|
||||
self.party_type = "Supplier"
|
||||
self.customer = "_Test Supplier USD"
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
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 = []
|
||||
@@ -931,6 +1011,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
if invoice.invoice_number == pi.name:
|
||||
invoices.append(invoice.as_dict())
|
||||
break
|
||||
|
||||
for payment in pr.payments:
|
||||
if payment.reference_name == pi_return.name:
|
||||
payments.append(payment.as_dict())
|
||||
@@ -941,6 +1022,121 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
||||
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 make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"is_advance",
|
||||
"section_break_5",
|
||||
"difference_amount",
|
||||
"gain_loss_posting_date",
|
||||
"column_break_7",
|
||||
"difference_account",
|
||||
"exchange_rate",
|
||||
@@ -151,11 +152,16 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "gain_loss_posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Difference Posting Date"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-03 07:52:33.684217",
|
||||
"modified": "2023-10-23 10:44:56.066303",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
|
||||
@@ -33,7 +33,7 @@ from erpnext.accounts.general_ledger import (
|
||||
)
|
||||
from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
@@ -284,9 +284,6 @@ class PurchaseInvoice(BuyingController):
|
||||
# in case of auto inventory accounting,
|
||||
# expense account is always "Stock Received But Not Billed" for a stock item
|
||||
# except opening entry, drop-ship entry and fixed asset items
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if (
|
||||
auto_accounting_for_stock
|
||||
and item.item_code in stock_items
|
||||
@@ -353,22 +350,26 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif item.is_fixed_asset:
|
||||
account_type = (
|
||||
"capital_work_in_progress_account"
|
||||
if is_cwip_accounting_enabled(item.asset_category)
|
||||
else "fixed_asset_account"
|
||||
)
|
||||
asset_category_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=item.item_code, company=self.company
|
||||
account_type, item=item.item_code, company=self.company
|
||||
)
|
||||
if not asset_category_account:
|
||||
form_link = get_link_to_form("Asset Category", asset_category)
|
||||
form_link = get_link_to_form("Asset Category", item.asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||
title=_("Missing Account"),
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||
|
||||
@@ -591,12 +592,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None):
|
||||
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||
|
||||
if self.auto_accounting_for_stock:
|
||||
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
||||
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
else:
|
||||
self.stock_received_but_not_billed = None
|
||||
self.expenses_included_in_valuation = None
|
||||
|
||||
self.negative_expense_to_be_booked = 0.0
|
||||
gl_entries = []
|
||||
@@ -605,9 +605,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_precision_loss_gl_entry(gl_entries)
|
||||
|
||||
if self.check_asset_cwip_enabled():
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
@@ -711,7 +708,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
|
||||
if (
|
||||
self.update_stock
|
||||
and self.auto_accounting_for_stock
|
||||
and (item.item_code in stock_items or item.is_fixed_asset)
|
||||
):
|
||||
# warehouse account
|
||||
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
||||
gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@@ -826,9 +827,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
elif not item.is_fixed_asset or (
|
||||
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
|
||||
):
|
||||
else:
|
||||
expense_account = (
|
||||
item.expense_account
|
||||
if (not item.enable_deferred_expense or self.is_return)
|
||||
@@ -921,40 +920,6 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
# If asset is bought through this document and not linked to PR
|
||||
if self.update_stock and item.landed_cost_voucher_amount:
|
||||
expenses_included_in_asset_valuation = self.get_company_default(
|
||||
"expenses_included_in_asset_valuation"
|
||||
)
|
||||
# Amount added through landed-cost-voucher
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expenses_included_in_asset_valuation,
|
||||
"against": expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": expenses_included_in_asset_valuation,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of asset bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
@@ -979,11 +944,17 @@ class PurchaseInvoice(BuyingController):
|
||||
(item.purchase_receipt, valuation_tax_accounts),
|
||||
)
|
||||
|
||||
stock_rbnb = (
|
||||
self.get_company_default("asset_received_but_not_billed")
|
||||
if item.is_fixed_asset
|
||||
else self.stock_received_but_not_billed
|
||||
)
|
||||
|
||||
if not negative_expense_booked_in_pr:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.stock_received_but_not_billed,
|
||||
"account": stock_rbnb,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
@@ -998,156 +969,12 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount, item.precision("item_tax_amount")
|
||||
)
|
||||
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
arbnb_account = None
|
||||
eiiav_account = None
|
||||
asset_eiiav_currency = None
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
|
||||
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
||||
|
||||
item_exp_acc_type = frappe.db.get_value("Account", item.expense_account, "account_type")
|
||||
if not item.expense_account or item_exp_acc_type not in [
|
||||
"Asset Received But Not Billed",
|
||||
"Fixed Asset",
|
||||
]:
|
||||
if not arbnb_account:
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = arbnb_account
|
||||
|
||||
if not self.update_stock:
|
||||
arbnb_currency = get_account_currency(item.expense_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if arbnb_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount:
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"credit": item.item_tax_amount,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
else:
|
||||
cwip_account = get_asset_account(
|
||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
||||
)
|
||||
|
||||
cwip_account_currency = get_account_currency(cwip_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": self.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"credit": item.item_tax_amount,
|
||||
"project": item.project or self.project,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# Assets are bought through this document then it will be linked to this document
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
if not eiiav_account:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
return gl_entries
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@@ -1849,6 +1676,7 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"po_detail": "purchase_order_item",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
|
||||
|
||||
@@ -1858,6 +1858,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
pi.load_from_db()
|
||||
self.assertFalse(pi.repost_required)
|
||||
|
||||
def test_default_cost_center_for_purchase(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
|
||||
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
|
||||
create_cost_center(cost_center_name=c_center)
|
||||
|
||||
item = create_item(
|
||||
"_Test Cost Center Item For Purchase",
|
||||
is_stock_item=1,
|
||||
buying_cost_center="_Test Cost Center Buying - _TC",
|
||||
selling_cost_center="_Test Cost Center Selling - _TC",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
|
||||
)
|
||||
|
||||
pi.items[0].cost_center = ""
|
||||
pi.set_missing_values()
|
||||
pi.calculate_taxes_and_totals()
|
||||
pi.save()
|
||||
|
||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||
|
||||
|
||||
def check_gl_entries(
|
||||
doc,
|
||||
|
||||
@@ -2497,12 +2497,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
"stock_received_but_not_billed",
|
||||
"Stock Received But Not Billed - _TC1",
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
"_Test Company 1",
|
||||
"expenses_included_in_valuation",
|
||||
"Expenses Included In Valuation - _TC1",
|
||||
)
|
||||
|
||||
# begin test
|
||||
si = create_sales_invoice(
|
||||
@@ -2540,7 +2534,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
# tear down
|
||||
frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
|
||||
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock)
|
||||
|
||||
def test_sle_for_target_warehouse(self):
|
||||
se = make_stock_entry(
|
||||
@@ -2552,6 +2546,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
)
|
||||
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
si.customer = "_Test Internal Customer 3"
|
||||
si.update_stock = 1
|
||||
si.set_warehouse = "Finished Goods - _TC"
|
||||
si.set_target_warehouse = "Stores - _TC"
|
||||
@@ -3579,6 +3574,20 @@ def create_internal_parties():
|
||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer 3",
|
||||
represents_company="_Test Company",
|
||||
allowed_to_interact_with="_Test Company",
|
||||
)
|
||||
|
||||
account = create_account(
|
||||
account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "unrealized_profit_loss_account", account)
|
||||
|
||||
create_internal_supplier(
|
||||
supplier_name="_Test Internal Supplier",
|
||||
represents_company="Wind Power LLC",
|
||||
|
||||
@@ -18,6 +18,14 @@ frappe.ui.form.on('Subscription', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('sales_tax_template', function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
@@ -41,7 +41,7 @@ def make_gl_entries(
|
||||
from_repost=from_repost,
|
||||
)
|
||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||
# Post GL Map proccess there may no be any GL Entries
|
||||
# Post GL Map process there may no be any GL Entries
|
||||
elif gl_map:
|
||||
frappe.throw(
|
||||
_(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from typing import Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe import _, msgprint, qb, scrub
|
||||
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
|
||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
||||
from frappe.model.utils import get_fetch_values
|
||||
@@ -459,11 +459,19 @@ def get_party_account_currency(party_type, party, company):
|
||||
|
||||
def get_party_gle_currency(party_type, party, company):
|
||||
def generator():
|
||||
existing_gle_currency = frappe.db.sql(
|
||||
"""select account_currency from `tabGL Entry`
|
||||
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
|
||||
limit 1""",
|
||||
{"company": company, "party_type": party_type, "party": party},
|
||||
gl = qb.DocType("GL Entry")
|
||||
existing_gle_currency = (
|
||||
qb.from_(gl)
|
||||
.select(gl.account_currency)
|
||||
.where(
|
||||
(gl.docstatus == 1)
|
||||
& (gl.company == company)
|
||||
& (gl.party_type == party_type)
|
||||
& (gl.party == party)
|
||||
& (gl.is_cancelled == 0)
|
||||
)
|
||||
.limit(1)
|
||||
.run()
|
||||
)
|
||||
|
||||
return existing_gle_currency[0][0] if existing_gle_currency else None
|
||||
|
||||
@@ -67,7 +67,7 @@ def setup_mappers(mappers):
|
||||
mapping["finance_costs"] = []
|
||||
mapping["finance_costs_adjustments"] = []
|
||||
doc = frappe.get_doc("Cash Flow Mapper", mapping["name"])
|
||||
mapping_names = [item.name for item in doc.accounts]
|
||||
mapping_names = [item.mapping for item in doc.accounts]
|
||||
|
||||
if not mapping_names:
|
||||
continue
|
||||
|
||||
@@ -33,13 +33,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"label": __("Accounting Dimension"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Accounting Dimension",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
|
||||
@@ -68,7 +68,7 @@ def get_result(
|
||||
tax_amount += entry.credit - entry.debit
|
||||
|
||||
if net_total_map.get(name):
|
||||
if voucher_type == "Journal Entry":
|
||||
if voucher_type == "Journal Entry" and tax_amount and rate:
|
||||
# back calcalute total amount from rate and tax_amount
|
||||
total_amount = grand_total = base_total = tax_amount / (rate / 100)
|
||||
else:
|
||||
|
||||
@@ -624,7 +624,7 @@ def update_reference_in_payment_entry(
|
||||
"outstanding_amount": d.outstanding_amount,
|
||||
"allocated_amount": d.allocated_amount,
|
||||
"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,
|
||||
}
|
||||
|
||||
if d.voucher_detail_no:
|
||||
@@ -636,28 +636,29 @@ def update_reference_in_payment_entry(
|
||||
existing_row.reference_doctype, existing_row.reference_name
|
||||
).set_total_advance_paid()
|
||||
|
||||
original_row = existing_row.as_dict().copy()
|
||||
existing_row.update(reference_details)
|
||||
if d.allocated_amount <= existing_row.allocated_amount:
|
||||
existing_row.allocated_amount -= d.allocated_amount
|
||||
|
||||
if d.allocated_amount < original_row.allocated_amount:
|
||||
new_row = payment_entry.append("references")
|
||||
new_row.docstatus = 1
|
||||
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:
|
||||
new_row = payment_entry.append("references")
|
||||
new_row.docstatus = 1
|
||||
new_row.update(reference_details)
|
||||
|
||||
payment_entry.flags.ignore_validate_update_after_submit = True
|
||||
payment_entry.clear_unallocated_reference_document_rows()
|
||||
payment_entry.setup_party_account_field()
|
||||
payment_entry.set_missing_values()
|
||||
if not skip_ref_details_update_for_pe:
|
||||
payment_entry.set_missing_ref_details()
|
||||
payment_entry.set_amounts()
|
||||
payment_entry.make_exchange_gain_loss_journal()
|
||||
payment_entry.make_exchange_gain_loss_journal(
|
||||
frappe._dict({"difference_posting_date": d.difference_posting_date})
|
||||
)
|
||||
|
||||
if not do_not_save:
|
||||
payment_entry.save(ignore_permissions=True)
|
||||
|
||||
@@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', {
|
||||
frm.set_query("item_code", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"disabled": 0,
|
||||
"is_fixed_asset": 1,
|
||||
"is_stock_item": 0
|
||||
}
|
||||
|
||||
@@ -221,11 +221,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)",
|
||||
"fieldname": "gross_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Gross Purchase Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -413,6 +413,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Receipt",
|
||||
@@ -430,6 +431,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
@@ -493,10 +495,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval.doc.asset_quantity",
|
||||
"fieldname": "asset_quantity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Asset Quantity",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "depr_entry_posting_status",
|
||||
@@ -555,9 +558,14 @@
|
||||
"link_doctype": "Journal Entry",
|
||||
"link_fieldname": "reference_name",
|
||||
"table_fieldname": "accounts"
|
||||
},
|
||||
{
|
||||
"group": "Asset Capitalization",
|
||||
"link_doctype": "Asset Capitalization",
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-03 23:28:26.732269",
|
||||
"modified": "2023-10-27 17:03:46.629617",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -186,6 +186,7 @@ class TestAsset(AssetSetup):
|
||||
def test_is_fixed_asset_set(self):
|
||||
asset = create_asset(is_existing_asset=1)
|
||||
doc = frappe.new_doc("Purchase Invoice")
|
||||
doc.company = "_Test Company"
|
||||
doc.supplier = "_Test Supplier"
|
||||
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
|
||||
|
||||
@@ -487,7 +488,7 @@ class TestAsset(AssetSetup):
|
||||
|
||||
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
|
||||
|
||||
# CWIP: Capital Work In Progress
|
||||
# Capital Work In Progress
|
||||
def test_cwip_accounting(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
|
||||
@@ -520,7 +521,8 @@ class TestAsset(AssetSetup):
|
||||
pr.submit()
|
||||
|
||||
expected_gle = (
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||
("_Test Account Shipping Charges - _TC", 0.0, 250.0),
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
|
||||
("CWIP Account - _TC", 5250.0, 0.0),
|
||||
)
|
||||
|
||||
@@ -539,9 +541,8 @@ class TestAsset(AssetSetup):
|
||||
expected_gle = (
|
||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5000.0, 0.0),
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||
)
|
||||
|
||||
pi_gle = frappe.db.sql(
|
||||
@@ -1731,6 +1732,7 @@ def create_asset_category():
|
||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||
"depreciation_expense_account": "_Test Depreciations - _TC",
|
||||
"capital_work_in_progress_account": "CWIP Account - _TC",
|
||||
},
|
||||
)
|
||||
asset_category.append(
|
||||
|
||||
@@ -832,12 +832,8 @@ def get_items_tagged_to_wip_composite_asset(asset):
|
||||
"amount",
|
||||
]
|
||||
|
||||
pi_items = frappe.get_all(
|
||||
"Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||
)
|
||||
|
||||
pr_items = frappe.get_all(
|
||||
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||
)
|
||||
|
||||
return pi_items + pr_items
|
||||
return pr_items
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"transaction_settings_section",
|
||||
"po_required",
|
||||
"pr_required",
|
||||
"over_order_allowance",
|
||||
"blanket_order_allowance",
|
||||
"column_break_12",
|
||||
"maintain_same_rate",
|
||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||
@@ -159,19 +159,19 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Landed Cost Based on Purchase Invoice Rate"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
|
||||
"fieldname": "over_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Over Order Allowance (%)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
|
||||
"fieldname": "use_transaction_date_exchange_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Transaction Date Exchange Rate"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Percentage you are allowed to order beyond the Blanket Order quantity.",
|
||||
"fieldname": "blanket_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Blanket Order Allowance (%)"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -179,7 +179,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-16 16:22:03.201078",
|
||||
"modified": "2023-10-25 14:03:32.520418",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -522,6 +522,9 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"bom": "bom",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item",
|
||||
"sales_order": "sales_order",
|
||||
"sales_order_item": "sales_order_item",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
@@ -598,6 +601,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
"field_map": {
|
||||
"name": "po_detail",
|
||||
"parent": "purchase_order",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
"billed_amt",
|
||||
"accounting_details",
|
||||
"expense_account",
|
||||
"column_break_fyqr",
|
||||
"wip_composite_asset",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"manufacturer_part_no",
|
||||
@@ -897,13 +899,23 @@
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_composite_asset",
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fyqr",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-13 16:22:40.825092",
|
||||
"modified": "2023-10-27 15:50:42.655573",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -44,11 +44,6 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
filters: { "disabled": 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1131,7 +1131,9 @@ class AccountsController(TransactionBase):
|
||||
self.name,
|
||||
arg.get("referenced_row"),
|
||||
):
|
||||
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date")
|
||||
posting_date = arg.get("difference_posting_date") or frappe.db.get_value(
|
||||
arg.voucher_type, arg.voucher_no, "posting_date"
|
||||
)
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
posting_date,
|
||||
@@ -1214,7 +1216,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
self.posting_date,
|
||||
args.get("difference_posting_date") if args else self.posting_date,
|
||||
self.party_type,
|
||||
self.party,
|
||||
party_account,
|
||||
|
||||
@@ -62,9 +62,12 @@ class StockController(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
|
||||
|
||||
if (
|
||||
cint(erpnext.is_perpetual_inventory_enabled(self.company))
|
||||
or provisional_accounting_for_non_stock_items
|
||||
or is_asset_pr
|
||||
):
|
||||
warehouse_account = get_warehouse_account_map(self.company)
|
||||
|
||||
@@ -73,11 +76,6 @@ class StockController(AccountsController):
|
||||
gl_entries = self.get_gl_entries(warehouse_account)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
|
||||
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
|
||||
gl_entries = []
|
||||
gl_entries = self.get_asset_gl_entry(gl_entries)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
|
||||
def validate_serialized_batch(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
@@ -670,13 +668,21 @@ class StockController(AccountsController):
|
||||
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
|
||||
|
||||
def validate_internal_transfer(self):
|
||||
if (
|
||||
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
|
||||
and self.is_internal_transfer()
|
||||
):
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"):
|
||||
if self.is_internal_transfer():
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
else:
|
||||
self.validate_internal_transfer_warehouse()
|
||||
|
||||
def validate_internal_transfer_warehouse(self):
|
||||
for row in self.items:
|
||||
if row.get("target_warehouse"):
|
||||
row.target_warehouse = None
|
||||
|
||||
if row.get("from_warehouse"):
|
||||
row.from_warehouse = None
|
||||
|
||||
def validate_in_transit_warehouses(self):
|
||||
if (
|
||||
|
||||
@@ -7,7 +7,7 @@ import frappe
|
||||
from frappe import qb
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, nowdate
|
||||
from frappe.utils import add_days, flt, getdate, nowdate
|
||||
|
||||
from erpnext import get_default_cost_center
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
@@ -614,6 +614,73 @@ class TestAccountsController(FrappeTestCase):
|
||||
self.assertEqual(exc_je_for_si, [])
|
||||
self.assertEqual(exc_je_for_pe, [])
|
||||
|
||||
def test_15_gain_loss_on_different_posting_date(self):
|
||||
# Invoice in Foreign Currency
|
||||
si = self.create_sales_invoice(
|
||||
posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1
|
||||
)
|
||||
# Payment
|
||||
pe = (
|
||||
self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75)
|
||||
.save()
|
||||
.submit()
|
||||
)
|
||||
|
||||
# There should be outstanding in both currencies
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 2)
|
||||
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
||||
|
||||
# Reconcile the remaining amount
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
pr.party_type = "Customer"
|
||||
pr.party = self.customer
|
||||
pr.receivable_payable_account = self.debit_usd
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
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.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1)
|
||||
pr.reconcile()
|
||||
|
||||
# Exchange Gain/Loss Journal should've been created.
|
||||
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
||||
self.assertNotEqual(exc_je_for_si, [])
|
||||
self.assertEqual(len(exc_je_for_si), 1)
|
||||
self.assertEqual(len(exc_je_for_pe), 1)
|
||||
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"),
|
||||
getdate(add_days(nowdate(), 1)),
|
||||
)
|
||||
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
# There should be no outstanding
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 0)
|
||||
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
||||
|
||||
# Cancel Payment
|
||||
pe.reload()
|
||||
pe.cancel()
|
||||
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 2)
|
||||
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
||||
|
||||
# Exchange Gain/Loss Journal should've been cancelled
|
||||
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
||||
self.assertEqual(exc_je_for_si, [])
|
||||
self.assertEqual(exc_je_for_pe, [])
|
||||
|
||||
def test_20_journal_against_sales_invoice(self):
|
||||
# Invoice in Foreign Currency
|
||||
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
||||
|
||||
@@ -411,11 +411,6 @@ def close_unsecured_term_loan(loan):
|
||||
frappe.throw(_("Cannot close this loan until full repayment"))
|
||||
|
||||
|
||||
def close_loan(loan, total_amount_paid):
|
||||
frappe.db.set_value("Loan", loan, "total_amount_paid", total_amount_paid)
|
||||
frappe.db.set_value("Loan", loan, "status", "Closed")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amount=0, as_dict=0):
|
||||
disbursement_entry = frappe.new_doc("Loan Disbursement")
|
||||
|
||||
@@ -9,6 +9,9 @@ from frappe.utils import cint, flt, getdate
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
|
||||
get_pending_principal_amount,
|
||||
)
|
||||
|
||||
|
||||
class LoanWriteOff(AccountsController):
|
||||
@@ -39,11 +42,13 @@ class LoanWriteOff(AccountsController):
|
||||
def on_submit(self):
|
||||
self.update_outstanding_amount()
|
||||
self.make_gl_entries()
|
||||
self.close_employee_loan()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_outstanding_amount(cancel=1)
|
||||
self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.close_employee_loan(cancel=1)
|
||||
|
||||
def update_outstanding_amount(self, cancel=0):
|
||||
written_off_amount = frappe.db.get_value("Loan", self.loan, "written_off_amount")
|
||||
@@ -94,3 +99,39 @@ class LoanWriteOff(AccountsController):
|
||||
)
|
||||
|
||||
make_gl_entries(gl_entries, cancel=cancel, merge_entries=False)
|
||||
|
||||
def close_employee_loan(self, cancel=0):
|
||||
if not frappe.db.has_column("Loan", "repay_from_salary"):
|
||||
return
|
||||
|
||||
loan = frappe.get_value(
|
||||
"Loan",
|
||||
self.loan,
|
||||
[
|
||||
"total_payment",
|
||||
"total_principal_paid",
|
||||
"loan_amount",
|
||||
"total_interest_payable",
|
||||
"written_off_amount",
|
||||
"disbursed_amount",
|
||||
"status",
|
||||
"is_secured_loan",
|
||||
"repay_from_salary",
|
||||
"name",
|
||||
],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if loan.is_secured_loan or not loan.repay_from_salary:
|
||||
return
|
||||
|
||||
if not cancel:
|
||||
pending_principal_amount = get_pending_principal_amount(loan)
|
||||
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
if flt(pending_principal_amount, precision) <= 0:
|
||||
frappe.db.set_value("Loan", loan.name, "status", "Closed")
|
||||
frappe.msgprint(_("Loan {0} closed").format(loan.name))
|
||||
else:
|
||||
frappe.db.set_value("Loan", loan.loan, "status", "Disbursed")
|
||||
|
||||
@@ -107,7 +107,7 @@ def validate_against_blanket_order(order_doc):
|
||||
allowance = flt(
|
||||
frappe.db.get_single_value(
|
||||
"Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings",
|
||||
"over_order_allowance",
|
||||
"blanket_order_allowance",
|
||||
)
|
||||
)
|
||||
for bo_name, item_data in order_data.items():
|
||||
|
||||
@@ -63,7 +63,7 @@ class TestBlanketOrder(FrappeTestCase):
|
||||
po1.currency = get_company_currency(po1.company)
|
||||
self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
|
||||
|
||||
def test_over_order_allowance(self):
|
||||
def test_blanket_order_allowance(self):
|
||||
# Sales Order
|
||||
bo = make_blanket_order(blanket_order_type="Selling", quantity=100)
|
||||
|
||||
@@ -74,7 +74,7 @@ class TestBlanketOrder(FrappeTestCase):
|
||||
so.items[0].qty = 110
|
||||
self.assertRaises(frappe.ValidationError, so.submit)
|
||||
|
||||
frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10)
|
||||
frappe.db.set_single_value("Selling Settings", "blanket_order_allowance", 10)
|
||||
so.submit()
|
||||
|
||||
# Purchase Order
|
||||
@@ -87,7 +87,7 @@ class TestBlanketOrder(FrappeTestCase):
|
||||
po.items[0].qty = 110
|
||||
self.assertRaises(frappe.ValidationError, po.submit)
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10)
|
||||
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
|
||||
po.submit()
|
||||
|
||||
|
||||
|
||||
@@ -1172,12 +1172,12 @@ def get_children(parent=None, is_root=False, **filters):
|
||||
def add_additional_cost(stock_entry, work_order):
|
||||
# Add non stock items cost in the additional cost
|
||||
stock_entry.additional_costs = []
|
||||
expenses_included_in_valuation = frappe.get_cached_value(
|
||||
"Company", work_order.company, "expenses_included_in_valuation"
|
||||
default_expense_account = frappe.get_cached_value(
|
||||
"Company", work_order.company, "default_expense_account"
|
||||
)
|
||||
|
||||
add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation)
|
||||
add_operations_cost(stock_entry, work_order, expenses_included_in_valuation)
|
||||
add_non_stock_items_cost(stock_entry, work_order, default_expense_account)
|
||||
add_operations_cost(stock_entry, work_order, default_expense_account)
|
||||
|
||||
|
||||
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
|
||||
|
||||
@@ -10,8 +10,8 @@ frappe.views.calendar["Job Card"] = {
|
||||
},
|
||||
gantt: {
|
||||
field_map: {
|
||||
"start": "started_time",
|
||||
"end": "started_time",
|
||||
"start": "expected_start_date",
|
||||
"end": "expected_end_date",
|
||||
"id": "name",
|
||||
"title": "subject",
|
||||
"color": "color",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
frappe.listview_settings['Job Card'] = {
|
||||
has_indicator_for_draft: true,
|
||||
|
||||
add_fields: ["expected_start_date", "expected_end_date"],
|
||||
get_indicator: function(doc) {
|
||||
const status_colors = {
|
||||
"Work In Progress": "orange",
|
||||
|
||||
@@ -1734,7 +1734,10 @@ def get_raw_materials_of_sub_assembly_items(
|
||||
if not item.conversion_factor and item.purchase_uom:
|
||||
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
|
||||
|
||||
item_details.setdefault(item.get("item_code"), item)
|
||||
if details := item_details.get(item.get("item_code")):
|
||||
details.qty += item.get("qty")
|
||||
else:
|
||||
item_details.setdefault(item.get("item_code"), item)
|
||||
|
||||
return item_details
|
||||
|
||||
|
||||
@@ -1321,6 +1321,33 @@ class TestProductionPlan(FrappeTestCase):
|
||||
self.assertTrue(row.warehouse == mrp_warhouse)
|
||||
self.assertEqual(row.quantity, 12)
|
||||
|
||||
def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
bom_tree = {
|
||||
"Fininshed Goods2 For SUB Test": {
|
||||
"SubAssembly2 For SUB Test": {"ChildPart2 For SUB Test": {}},
|
||||
"SubAssembly3 For SUB Test": {"ChildPart2 For SUB Test": {}},
|
||||
}
|
||||
}
|
||||
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=1,
|
||||
ignore_existing_ordered_qty=1,
|
||||
do_not_submit=1,
|
||||
skip_available_sub_assembly_item=1,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
plan.get_sub_assembly_items()
|
||||
plan.make_material_request()
|
||||
|
||||
for row in plan.mr_items:
|
||||
if row.item_code == "ChildPart2 For SUB Test":
|
||||
self.assertEqual(row.quantity, 2)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -12,7 +12,7 @@ frappe.query_reports["BOM Operations Time"] = {
|
||||
"options": "Item",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: { "disabled": 0, "is_stock_item": 1 }
|
||||
filters: { "is_stock_item": 1 }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -342,5 +342,7 @@ execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for
|
||||
erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow
|
||||
erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item
|
||||
erpnext.patches.v14_0.rename_over_order_allowance_field
|
||||
erpnext.patches.v14_0.migrate_delivery_stop_lock_field
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Landed Cost Taxes and Charges")
|
||||
|
||||
company_account_map = frappe._dict(
|
||||
frappe.db.sql(
|
||||
"""
|
||||
SELECT name, expenses_included_in_valuation from `tabCompany`
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
for company, account in company_account_map.items():
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE
|
||||
`tabLanded Cost Taxes and Charges` t, `tabLanded Cost Voucher` l
|
||||
SET
|
||||
t.expense_account = %s
|
||||
WHERE
|
||||
l.docstatus = 1
|
||||
AND l.company = %s
|
||||
AND t.parent = l.name
|
||||
""",
|
||||
(account, company),
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE
|
||||
`tabLanded Cost Taxes and Charges` t, `tabStock Entry` s
|
||||
SET
|
||||
t.expense_account = %s
|
||||
WHERE
|
||||
s.docstatus = 1
|
||||
AND s.company = %s
|
||||
AND t.parent = s.name
|
||||
""",
|
||||
(account, company),
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
import frappe
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.has_column("Delivery Stop", "lock"):
|
||||
rename_field("Delivery Stop", "lock", "locked")
|
||||
15
erpnext/patches/v14_0/rename_over_order_allowance_field.py
Normal file
15
erpnext/patches/v14_0/rename_over_order_allowance_field.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
|
||||
|
||||
def execute():
|
||||
rename_field(
|
||||
"Buying Settings",
|
||||
"over_order_allowance",
|
||||
"blanket_order_allowance",
|
||||
)
|
||||
|
||||
rename_field(
|
||||
"Selling Settings",
|
||||
"over_order_allowance",
|
||||
"blanket_order_allowance",
|
||||
)
|
||||
@@ -28,7 +28,6 @@ frappe.ui.form.on(cur_frm.doctype, {
|
||||
filters: {
|
||||
"account_type": account_type,
|
||||
"company": doc.company,
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ erpnext.accounts.unreconcile_payments = {
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.add_custom_button(__("Un-Reconcile"), function() {
|
||||
frm.add_custom_button(__("UnReconcile"), function() {
|
||||
erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm);
|
||||
}, __('Actions'));
|
||||
}
|
||||
@@ -87,11 +87,11 @@ erpnext.accounts.unreconcile_payments = {
|
||||
unreconcile_dialog_fields[0].get_data = function(){ return r.message};
|
||||
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: 'Un-Reconcile Allocations',
|
||||
title: 'UnReconcile Allocations',
|
||||
fields: unreconcile_dialog_fields,
|
||||
size: 'large',
|
||||
cannot_add_rows: true,
|
||||
primary_action_label: 'Un-Reconcile',
|
||||
primary_action_label: 'UnReconcile',
|
||||
primary_action(values) {
|
||||
|
||||
let selected_allocations = values.allocations.filter(x=>x.__checked);
|
||||
|
||||
@@ -1631,7 +1631,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-20 11:14:01.036202",
|
||||
"modified": "2023-10-18 12:41:54.813462",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
@@ -1893,10 +1893,10 @@ class TestSalesOrder(FrappeTestCase):
|
||||
si.submit()
|
||||
pe.load_from_db()
|
||||
|
||||
self.assertEqual(pe.references[0].reference_name, si.name)
|
||||
self.assertEqual(pe.references[0].allocated_amount, 200)
|
||||
self.assertEqual(pe.references[1].reference_name, so.name)
|
||||
self.assertEqual(pe.references[1].allocated_amount, 300)
|
||||
self.assertEqual(pe.references[0].reference_name, so.name)
|
||||
self.assertEqual(pe.references[0].allocated_amount, 300)
|
||||
self.assertEqual(pe.references[1].reference_name, si.name)
|
||||
self.assertEqual(pe.references[1].allocated_amount, 200)
|
||||
|
||||
def test_delivered_item_material_request(self):
|
||||
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"so_required",
|
||||
"dn_required",
|
||||
"sales_update_frequency",
|
||||
"over_order_allowance",
|
||||
"blanket_order_allowance",
|
||||
"column_break_5",
|
||||
"allow_multiple_items",
|
||||
"allow_against_multiple_purchase_orders",
|
||||
@@ -182,17 +182,17 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Sales Order Creation For Expired Quotation"
|
||||
},
|
||||
{
|
||||
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
|
||||
"fieldname": "over_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Over Order Allowance (%)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_negative_rates_for_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Negative rates for Items"
|
||||
},
|
||||
{
|
||||
"description": "Percentage you are allowed to sell beyond the Blanket Order quantity.",
|
||||
"fieldname": "blanket_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Blanket Order Allowance (%)"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -200,7 +200,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-14 20:33:05.693667",
|
||||
"modified": "2023-10-25 14:03:03.966701",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
@@ -223,7 +223,6 @@ erpnext.company.setup_queries = function(frm) {
|
||||
["cost_center", {}],
|
||||
["round_off_cost_center", {}],
|
||||
["depreciation_cost_center", {}],
|
||||
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
|
||||
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
|
||||
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
|
||||
["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}],
|
||||
@@ -236,8 +235,6 @@ erpnext.company.setup_queries = function(frm) {
|
||||
$.each([
|
||||
["stock_adjustment_account",
|
||||
{"root_type": "Expense", "account_type": "Stock Adjustment"}],
|
||||
["expenses_included_in_valuation",
|
||||
{"root_type": "Expense", "account_type": "Expenses Included in Valuation"}],
|
||||
["stock_received_but_not_billed",
|
||||
{"root_type": "Liability", "account_type": "Stock Received But Not Billed"}],
|
||||
["service_received_but_not_billed",
|
||||
|
||||
@@ -79,12 +79,10 @@
|
||||
"column_break_32",
|
||||
"stock_received_but_not_billed",
|
||||
"default_provisional_account",
|
||||
"expenses_included_in_valuation",
|
||||
"fixed_asset_defaults",
|
||||
"accumulated_depreciation_account",
|
||||
"depreciation_expense_account",
|
||||
"series_for_depreciation_entry",
|
||||
"expenses_included_in_asset_valuation",
|
||||
"column_break_40",
|
||||
"disposal_account",
|
||||
"depreciation_cost_center",
|
||||
@@ -466,14 +464,6 @@
|
||||
"no_copy": 1,
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "expenses_included_in_valuation",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Expenses Included In Valuation",
|
||||
"no_copy": 1,
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "accumulated_depreciation_account",
|
||||
"fieldtype": "Link",
|
||||
@@ -493,12 +483,6 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Series for Asset Depreciation Entry (Journal Entry)"
|
||||
},
|
||||
{
|
||||
"fieldname": "expenses_included_in_asset_valuation",
|
||||
"fieldtype": "Link",
|
||||
"label": "Expenses Included In Asset Valuation",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_40",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -728,7 +712,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-07 05:41:41.537256",
|
||||
"modified": "2023-10-23 10:19:24.322898",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
||||
@@ -92,7 +92,6 @@ class Company(NestedSet):
|
||||
["Default Income Account", "default_income_account"],
|
||||
["Stock Received But Not Billed Account", "stock_received_but_not_billed"],
|
||||
["Stock Adjustment Account", "stock_adjustment_account"],
|
||||
["Expense Included In Valuation Account", "expenses_included_in_valuation"],
|
||||
]
|
||||
|
||||
for account in accounts:
|
||||
@@ -384,7 +383,6 @@ class Company(NestedSet):
|
||||
"depreciation_expense_account": "Depreciation",
|
||||
"capital_work_in_progress_account": "Capital Work in Progress",
|
||||
"asset_received_but_not_billed": "Asset Received But Not Billed",
|
||||
"expenses_included_in_asset_valuation": "Expenses Included In Asset Valuation",
|
||||
"default_expense_account": "Cost of Goods Sold",
|
||||
}
|
||||
|
||||
@@ -394,7 +392,6 @@ class Company(NestedSet):
|
||||
"stock_received_but_not_billed": "Stock Received But Not Billed",
|
||||
"default_inventory_account": "Stock",
|
||||
"stock_adjustment_account": "Stock Adjustment",
|
||||
"expenses_included_in_valuation": "Expenses Included In Valuation",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1315,6 +1315,21 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
frappe.db.rollback()
|
||||
frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
|
||||
|
||||
def test_non_internal_transfer_delivery_note(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
dn = create_delivery_note(do_not_submit=True)
|
||||
warehouse = create_warehouse("Internal Transfer Warehouse", company=dn.company)
|
||||
dn.items[0].db_set("target_warehouse", warehouse)
|
||||
|
||||
dn.reload()
|
||||
|
||||
self.assertEqual(dn.items[0].target_warehouse, warehouse)
|
||||
|
||||
dn.save()
|
||||
dn.reload()
|
||||
self.assertFalse(dn.items[0].target_warehouse)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
||||
@@ -1,815 +1,197 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-10-16 16:46:28.166950",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2017-10-16 16:46:28.166950",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"customer",
|
||||
"address",
|
||||
"locked",
|
||||
"column_break_6",
|
||||
"customer_address",
|
||||
"visited",
|
||||
"order_information_section",
|
||||
"delivery_note",
|
||||
"cb_order",
|
||||
"grand_total",
|
||||
"section_break_7",
|
||||
"contact",
|
||||
"email_sent_to",
|
||||
"column_break_7",
|
||||
"customer_contact",
|
||||
"section_break_9",
|
||||
"distance",
|
||||
"estimated_arrival",
|
||||
"lat",
|
||||
"column_break_19",
|
||||
"uom",
|
||||
"lng",
|
||||
"more_information_section",
|
||||
"details"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Customer",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Customer",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Customer",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "address",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Address Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Address",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "address",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Address Name",
|
||||
"options": "Address",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "lock",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Lock",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "locked",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Locked"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "customer_address",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Customer Address",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "customer_address",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Customer Address",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.docstatus==1",
|
||||
"fieldname": "visited",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Visited",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus==1",
|
||||
"fieldname": "visited",
|
||||
"fieldtype": "Check",
|
||||
"label": "Visited",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "order_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Order Information",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "order_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Order Information"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Delivery Note",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Delivery Note",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Delivery Note",
|
||||
"no_copy": 1,
|
||||
"options": "Delivery Note",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cb_order",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "cb_order",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Grand Total",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Grand Total",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Contact Information",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Contact Information"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "contact",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Contact Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Contact",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "contact",
|
||||
"fieldtype": "Link",
|
||||
"label": "Contact Name",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "email_sent_to",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Email sent to",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "email_sent_to",
|
||||
"fieldtype": "Data",
|
||||
"label": "Email sent to",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "customer_contact",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Customer Contact",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "customer_contact",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Customer Contact",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Dispatch Information",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dispatch Information"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "distance",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Distance",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "distance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Distance",
|
||||
"precision": "2",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "estimated_arrival",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Estimated Arrival",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "estimated_arrival",
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
"label": "Estimated Arrival"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "lat",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Latitude",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "lat",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Latitude"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "eval:doc.distance",
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "UOM",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "UOM",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval:doc.distance",
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "UOM",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "lng",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Longitude",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "lng",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Longitude"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "more_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "More Information",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "more_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "details",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "details",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Details"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-10-16 05:23:25.661542",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Stop",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-29 09:22:53.435161",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Stop",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -62,8 +62,13 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
company: frm.doc.company,
|
||||
}
|
||||
})
|
||||
}, __("Get customers from"));
|
||||
}, __("Get stops from"));
|
||||
}
|
||||
frm.add_custom_button(__("Delivery Notes"), function () {
|
||||
frappe.set_route("List", "Delivery Note",
|
||||
{'name': ["in", frm.doc.delivery_stops.map((stop) => {return stop.delivery_note;})]}
|
||||
);
|
||||
}, __("View"));
|
||||
},
|
||||
|
||||
calculate_arrival_time: function (frm) {
|
||||
|
||||
@@ -170,7 +170,7 @@ class DeliveryTrip(Document):
|
||||
for stop in self.delivery_stops:
|
||||
leg.append(stop.customer_address)
|
||||
|
||||
if optimize and stop.lock:
|
||||
if optimize and stop.locked:
|
||||
route_list.append(leg)
|
||||
leg = [stop.customer_address]
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class TestDeliveryTrip(FrappeTestCase):
|
||||
self.assertEqual(len(route_list[0]), 4)
|
||||
|
||||
def test_unoptimized_route_list_with_locks(self):
|
||||
self.delivery_trip.delivery_stops[0].lock = 1
|
||||
self.delivery_trip.delivery_stops[0].locked = 1
|
||||
self.delivery_trip.save()
|
||||
route_list = self.delivery_trip.form_route_list(optimize=False)
|
||||
|
||||
@@ -65,7 +65,7 @@ class TestDeliveryTrip(FrappeTestCase):
|
||||
self.assertEqual(len(route_list[0]), 4)
|
||||
|
||||
def test_optimized_route_list_with_locks(self):
|
||||
self.delivery_trip.delivery_stops[0].lock = 1
|
||||
self.delivery_trip.delivery_stops[0].locked = 1
|
||||
self.delivery_trip.save()
|
||||
route_list = self.delivery_trip.form_route_list(optimize=True)
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ class Item(Document):
|
||||
|
||||
# add item taxes from template
|
||||
for d in template.get("taxes"):
|
||||
self.append("taxes", {"item_tax_template": d.item_tax_template})
|
||||
self.append("taxes", d)
|
||||
|
||||
# copy re-order table if empty
|
||||
if not self.get("reorder_levels"):
|
||||
|
||||
@@ -893,6 +893,8 @@ def create_item(
|
||||
opening_stock=0,
|
||||
is_fixed_asset=0,
|
||||
asset_category=None,
|
||||
buying_cost_center=None,
|
||||
selling_cost_center=None,
|
||||
company="_Test Company",
|
||||
):
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
@@ -910,7 +912,15 @@ def create_item(
|
||||
item.is_purchase_item = is_purchase_item
|
||||
item.is_customer_provided_item = is_customer_provided_item
|
||||
item.customer = customer or ""
|
||||
item.append("item_defaults", {"default_warehouse": warehouse, "company": company})
|
||||
item.append(
|
||||
"item_defaults",
|
||||
{
|
||||
"default_warehouse": warehouse,
|
||||
"company": company,
|
||||
"selling_cost_center": selling_cost_center,
|
||||
"buying_cost_center": buying_cost_center,
|
||||
},
|
||||
)
|
||||
item.save()
|
||||
else:
|
||||
item = frappe.get_doc("Item", item_code)
|
||||
|
||||
@@ -6,7 +6,6 @@ frappe.ui.form.on("Item Price", {
|
||||
frm.set_query("item_code", function() {
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0,
|
||||
"has_variants": 0
|
||||
}
|
||||
};
|
||||
|
||||
@@ -405,6 +405,7 @@ def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
["uom", "uom"],
|
||||
["sales_order", "sales_order"],
|
||||
["sales_order_item", "sales_order_item"],
|
||||
["wip_composite_asset", "wip_composite_asset"],
|
||||
],
|
||||
"postprocess": update_item,
|
||||
"condition": select_item,
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"rate",
|
||||
"col_break3",
|
||||
"amount",
|
||||
"accounting_details_section",
|
||||
"expense_account",
|
||||
"column_break_glru",
|
||||
"wip_composite_asset",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"manufacturer_part_no",
|
||||
@@ -50,11 +54,10 @@
|
||||
"lead_time_date",
|
||||
"sales_order",
|
||||
"sales_order_item",
|
||||
"col_break4",
|
||||
"production_plan",
|
||||
"material_request_plan_item",
|
||||
"job_card_item",
|
||||
"col_break4",
|
||||
"expense_account",
|
||||
"section_break_46",
|
||||
"page_break"
|
||||
],
|
||||
@@ -455,13 +458,28 @@
|
||||
"label": "Job Card Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_glru",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_composite_asset",
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-07 20:23:31.250252",
|
||||
"modified": "2023-10-27 15:53:41.444236",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request Item",
|
||||
|
||||
@@ -13,7 +13,6 @@ from pypika import functions as fn
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction
|
||||
@@ -146,8 +145,8 @@ class PurchaseReceipt(BuyingController):
|
||||
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
|
||||
# check cwip accounts before making auto assets
|
||||
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
cwip_account = get_asset_account(
|
||||
self.get_company_default("asset_received_but_not_billed")
|
||||
get_asset_account(
|
||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
||||
)
|
||||
break
|
||||
@@ -315,7 +314,7 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
update_regional_gl_entries(gl_entries, self)
|
||||
|
||||
return process_gl_map(gl_entries)
|
||||
|
||||
@@ -324,14 +323,6 @@ class PurchaseReceipt(BuyingController):
|
||||
get_purchase_document_details,
|
||||
)
|
||||
|
||||
stock_rbnb = None
|
||||
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
|
||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
|
||||
warehouse_with_no_account = []
|
||||
stock_items = self.get_stock_items()
|
||||
provisional_accounting_for_non_stock_items = cint(
|
||||
frappe.db.get_value(
|
||||
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
|
||||
@@ -340,28 +331,258 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||
|
||||
def validate_account(account_type):
|
||||
frappe.throw(_("{0} account not found while submitting purchase receipt").format(account_type))
|
||||
|
||||
def make_item_asset_inward_gl_entry(item, stock_value_diff, stock_asset_account_name):
|
||||
account_currency = get_account_currency(stock_asset_account_name)
|
||||
|
||||
if not stock_asset_account_name:
|
||||
validate_account("Asset or warehouse account")
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=stock_asset_account_name,
|
||||
cost_center=d.cost_center,
|
||||
debit=stock_value_diff,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=stock_asset_rbnb,
|
||||
account_currency=account_currency,
|
||||
item=item,
|
||||
)
|
||||
|
||||
def make_stock_received_but_not_billed_entry(item):
|
||||
account = (
|
||||
warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb
|
||||
)
|
||||
account_currency = get_account_currency(account)
|
||||
|
||||
# GL Entry for from warehouse or Stock Received but not billed
|
||||
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||
credit_amount = (
|
||||
flt(item.base_net_amount, item.precision("base_net_amount"))
|
||||
if account_currency == self.company_currency
|
||||
else flt(item.net_amount, item.precision("net_amount"))
|
||||
)
|
||||
|
||||
outgoing_amount = item.base_net_amount
|
||||
if self.is_internal_transfer() and item.valuation_rate:
|
||||
outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse))
|
||||
credit_amount = outgoing_amount
|
||||
|
||||
if credit_amount:
|
||||
if not account:
|
||||
validate_account("Stock or Asset Received But Not Billed")
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=account,
|
||||
cost_center=item.cost_center,
|
||||
debit=-1 * flt(outgoing_amount, item.precision("base_net_amount")),
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=stock_asset_account_name,
|
||||
debit_in_account_currency=-1 * flt(outgoing_amount, item.precision("base_net_amount")),
|
||||
account_currency=account_currency,
|
||||
item=item,
|
||||
)
|
||||
|
||||
# check if the exchange rate has changed
|
||||
if d.get("purchase_invoice"):
|
||||
if (
|
||||
exchange_rate_map[item.purchase_invoice]
|
||||
and self.conversion_rate != exchange_rate_map[item.purchase_invoice]
|
||||
and item.net_rate == net_rate_map[item.purchase_invoice_item]
|
||||
):
|
||||
|
||||
discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * (
|
||||
exchange_rate_map[item.purchase_invoice] - self.conversion_rate
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=account,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=discrepancy_caused_by_exchange_rate_difference,
|
||||
remarks=remarks,
|
||||
against_account=self.supplier,
|
||||
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
account_currency=account_currency,
|
||||
item=item,
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=self.get_company_default("exchange_gain_loss_account"),
|
||||
cost_center=d.cost_center,
|
||||
debit=discrepancy_caused_by_exchange_rate_difference,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=self.supplier,
|
||||
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
account_currency=account_currency,
|
||||
item=item,
|
||||
)
|
||||
|
||||
return outgoing_amount
|
||||
|
||||
def make_landed_cost_gl_entries(item):
|
||||
# Amount added through landed-cost-voucher
|
||||
if item.landed_cost_voucher_amount and landed_cost_entries:
|
||||
if (item.item_code, item.name) in landed_cost_entries:
|
||||
for account, amount in landed_cost_entries[(item.item_code, item.name)].items():
|
||||
account_currency = get_account_currency(account)
|
||||
credit_amount = (
|
||||
flt(amount["base_amount"])
|
||||
if (amount["base_amount"] or account_currency != self.company_currency)
|
||||
else flt(amount["amount"])
|
||||
)
|
||||
|
||||
if not account:
|
||||
validate_account("Landed Cost Account")
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=account,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=credit_amount,
|
||||
remarks=remarks,
|
||||
against_account=stock_asset_account_name,
|
||||
credit_in_account_currency=flt(amount["amount"]),
|
||||
account_currency=account_currency,
|
||||
project=item.project,
|
||||
item=item,
|
||||
)
|
||||
|
||||
def make_rate_difference_entry(item):
|
||||
if item.rate_difference_with_purchase_invoice and stock_asset_rbnb:
|
||||
account_currency = get_account_currency(stock_asset_rbnb)
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=stock_asset_rbnb,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=flt(item.rate_difference_with_purchase_invoice),
|
||||
remarks=_("Adjustment based on Purchase Invoice rate"),
|
||||
against_account=stock_asset_account_name,
|
||||
account_currency=account_currency,
|
||||
project=item.project,
|
||||
item=item,
|
||||
)
|
||||
|
||||
def make_sub_contracting_gl_entries(item):
|
||||
# sub-contracting warehouse
|
||||
if flt(item.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=supplier_warehouse_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=flt(item.rm_supp_cost),
|
||||
remarks=remarks,
|
||||
against_account=stock_asset_account_name,
|
||||
account_currency=supplier_warehouse_account_currency,
|
||||
item=item,
|
||||
)
|
||||
|
||||
def make_divisional_loss_gl_entry(item, outgoing_amount):
|
||||
if item.is_fixed_asset:
|
||||
return
|
||||
|
||||
# divisional loss adjustment
|
||||
valuation_amount_as_per_doc = (
|
||||
flt(outgoing_amount, d.precision("base_net_amount"))
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
+ flt(item.rm_supp_cost)
|
||||
+ flt(item.item_tax_amount)
|
||||
+ flt(item.rate_difference_with_purchase_invoice)
|
||||
)
|
||||
|
||||
divisional_loss = flt(
|
||||
valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount")
|
||||
)
|
||||
|
||||
if divisional_loss:
|
||||
loss_account = (
|
||||
self.get_company_default("default_expense_account", ignore_validation=True)
|
||||
or stock_asset_rbnb
|
||||
)
|
||||
|
||||
cost_center = item.cost_center or frappe.get_cached_value(
|
||||
"Company", self.company, "cost_center"
|
||||
)
|
||||
account_currency = get_account_currency(loss_account)
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=loss_account,
|
||||
cost_center=cost_center,
|
||||
debit=divisional_loss,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=stock_asset_account_name,
|
||||
account_currency=account_currency,
|
||||
project=item.project,
|
||||
item=item,
|
||||
)
|
||||
|
||||
stock_items = self.get_stock_items()
|
||||
warehouse_with_no_account = []
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.item_code in stock_items and flt(d.qty) and (flt(d.valuation_rate) or self.is_return):
|
||||
if warehouse_account.get(d.warehouse):
|
||||
stock_value_diff = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"warehouse": d.warehouse,
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
"stock_value_difference",
|
||||
if (
|
||||
provisional_accounting_for_non_stock_items
|
||||
and d.item_code not in stock_items
|
||||
and flt(d.qty)
|
||||
and d.get("provisional_expense_account")
|
||||
and not d.is_fixed_asset
|
||||
):
|
||||
self.add_provisional_gl_entry(
|
||||
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
||||
)
|
||||
elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return):
|
||||
remarks = self.get("remarks") or _("Accounting Entry for {0}").format(
|
||||
"Asset" if d.is_fixed_asset else "Stock"
|
||||
)
|
||||
|
||||
if not (
|
||||
(erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items)
|
||||
or d.is_fixed_asset
|
||||
):
|
||||
continue
|
||||
|
||||
stock_asset_rbnb = (
|
||||
self.get_company_default("asset_received_but_not_billed")
|
||||
if d.is_fixed_asset
|
||||
else self.get_company_default("stock_received_but_not_billed")
|
||||
)
|
||||
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
|
||||
|
||||
if d.is_fixed_asset:
|
||||
account_type = (
|
||||
"capital_work_in_progress_account"
|
||||
if is_cwip_accounting_enabled(d.asset_category)
|
||||
else "fixed_asset_account"
|
||||
)
|
||||
|
||||
warehouse_account_name = warehouse_account[d.warehouse]["account"]
|
||||
warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"]
|
||||
stock_asset_account_name = get_asset_account(
|
||||
account_type, asset_category=d.asset_category, company=self.company
|
||||
)
|
||||
|
||||
stock_value_diff = (
|
||||
flt(d.net_amount)
|
||||
+ flt(d.item_tax_amount / self.conversion_rate)
|
||||
+ flt(d.landed_cost_voucher_amount)
|
||||
)
|
||||
elif warehouse_account.get(d.warehouse):
|
||||
stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
|
||||
stock_asset_account_name = warehouse_account[d.warehouse]["account"]
|
||||
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account")
|
||||
supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get(
|
||||
"account_currency"
|
||||
)
|
||||
remarks = self.get("remarks") or _("Accounting Entry for Stock")
|
||||
|
||||
# If PR is sub-contracted and fg item rate is zero
|
||||
# in that case if account for source and target warehouse are same,
|
||||
@@ -369,214 +590,25 @@ class PurchaseReceipt(BuyingController):
|
||||
if (
|
||||
flt(stock_value_diff) == flt(d.rm_supp_cost)
|
||||
and warehouse_account.get(self.supplier_warehouse)
|
||||
and warehouse_account_name == supplier_warehouse_account
|
||||
and stock_asset_account_name == supplier_warehouse_account
|
||||
):
|
||||
continue
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=warehouse_account_name,
|
||||
cost_center=d.cost_center,
|
||||
debit=stock_value_diff,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=stock_rbnb,
|
||||
account_currency=warehouse_account_currency,
|
||||
item=d,
|
||||
)
|
||||
|
||||
# GL Entry for from warehouse or Stock Received but not billed
|
||||
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||
credit_currency = (
|
||||
get_account_currency(warehouse_account[d.from_warehouse]["account"])
|
||||
if d.from_warehouse
|
||||
else get_account_currency(stock_rbnb)
|
||||
)
|
||||
|
||||
credit_amount = (
|
||||
flt(d.base_net_amount, d.precision("base_net_amount"))
|
||||
if credit_currency == self.company_currency
|
||||
else flt(d.net_amount, d.precision("net_amount"))
|
||||
)
|
||||
|
||||
outgoing_amount = d.base_net_amount
|
||||
if self.is_internal_transfer() and d.valuation_rate:
|
||||
outgoing_amount = abs(
|
||||
frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"warehouse": d.from_warehouse,
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
"stock_value_difference",
|
||||
)
|
||||
)
|
||||
credit_amount = outgoing_amount
|
||||
|
||||
if credit_amount:
|
||||
account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=account,
|
||||
cost_center=d.cost_center,
|
||||
debit=-1 * flt(outgoing_amount, d.precision("base_net_amount")),
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=warehouse_account_name,
|
||||
debit_in_account_currency=-1 * credit_amount,
|
||||
account_currency=credit_currency,
|
||||
item=d,
|
||||
)
|
||||
|
||||
# check if the exchange rate has changed
|
||||
if d.get("purchase_invoice"):
|
||||
if (
|
||||
exchange_rate_map[d.purchase_invoice]
|
||||
and self.conversion_rate != exchange_rate_map[d.purchase_invoice]
|
||||
and d.net_rate == net_rate_map[d.purchase_invoice_item]
|
||||
):
|
||||
|
||||
discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * (
|
||||
exchange_rate_map[d.purchase_invoice] - self.conversion_rate
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=account,
|
||||
cost_center=d.cost_center,
|
||||
debit=0.0,
|
||||
credit=discrepancy_caused_by_exchange_rate_difference,
|
||||
remarks=remarks,
|
||||
against_account=self.supplier,
|
||||
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
account_currency=credit_currency,
|
||||
item=d,
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=self.get_company_default("exchange_gain_loss_account"),
|
||||
cost_center=d.cost_center,
|
||||
debit=discrepancy_caused_by_exchange_rate_difference,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=self.supplier,
|
||||
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
account_currency=credit_currency,
|
||||
item=d,
|
||||
)
|
||||
|
||||
# Amount added through landed-cos-voucher
|
||||
if d.landed_cost_voucher_amount and landed_cost_entries:
|
||||
if (d.item_code, d.name) in landed_cost_entries:
|
||||
for account, amount in landed_cost_entries[(d.item_code, d.name)].items():
|
||||
account_currency = get_account_currency(account)
|
||||
credit_amount = (
|
||||
flt(amount["base_amount"])
|
||||
if (amount["base_amount"] or account_currency != self.company_currency)
|
||||
else flt(amount["amount"])
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=account,
|
||||
cost_center=d.cost_center,
|
||||
debit=0.0,
|
||||
credit=credit_amount,
|
||||
remarks=remarks,
|
||||
against_account=warehouse_account_name,
|
||||
credit_in_account_currency=flt(amount["amount"]),
|
||||
account_currency=account_currency,
|
||||
project=d.project,
|
||||
item=d,
|
||||
)
|
||||
|
||||
if d.rate_difference_with_purchase_invoice and stock_rbnb:
|
||||
account_currency = get_account_currency(stock_rbnb)
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=stock_rbnb,
|
||||
cost_center=d.cost_center,
|
||||
debit=0.0,
|
||||
credit=flt(d.rate_difference_with_purchase_invoice),
|
||||
remarks=_("Adjustment based on Purchase Invoice rate"),
|
||||
against_account=warehouse_account_name,
|
||||
account_currency=account_currency,
|
||||
project=d.project,
|
||||
item=d,
|
||||
)
|
||||
|
||||
# sub-contracting warehouse
|
||||
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=supplier_warehouse_account,
|
||||
cost_center=d.cost_center,
|
||||
debit=0.0,
|
||||
credit=flt(d.rm_supp_cost),
|
||||
remarks=remarks,
|
||||
against_account=warehouse_account_name,
|
||||
account_currency=supplier_warehouse_account_currency,
|
||||
item=d,
|
||||
)
|
||||
|
||||
# divisional loss adjustment
|
||||
valuation_amount_as_per_doc = (
|
||||
flt(outgoing_amount, d.precision("base_net_amount"))
|
||||
+ flt(d.landed_cost_voucher_amount)
|
||||
+ flt(d.rm_supp_cost)
|
||||
+ flt(d.item_tax_amount)
|
||||
+ flt(d.rate_difference_with_purchase_invoice)
|
||||
)
|
||||
|
||||
divisional_loss = flt(
|
||||
valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount")
|
||||
)
|
||||
|
||||
if divisional_loss:
|
||||
if self.is_return or flt(d.item_tax_amount):
|
||||
loss_account = expenses_included_in_valuation
|
||||
else:
|
||||
loss_account = (
|
||||
self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
|
||||
)
|
||||
|
||||
cost_center = d.cost_center or frappe.get_cached_value(
|
||||
"Company", self.company, "cost_center"
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=loss_account,
|
||||
cost_center=cost_center,
|
||||
debit=divisional_loss,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=warehouse_account_name,
|
||||
account_currency=credit_currency,
|
||||
project=d.project,
|
||||
item=d,
|
||||
)
|
||||
|
||||
elif (
|
||||
d.warehouse not in warehouse_with_no_account
|
||||
or d.rejected_warehouse not in warehouse_with_no_account
|
||||
):
|
||||
warehouse_with_no_account.append(d.warehouse)
|
||||
if (flt(d.valuation_rate) or self.is_return or d.is_fixed_asset) and flt(d.qty):
|
||||
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
|
||||
outgoing_amount = make_stock_received_but_not_billed_entry(d)
|
||||
make_landed_cost_gl_entries(d)
|
||||
make_rate_difference_entry(d)
|
||||
make_sub_contracting_gl_entries(d)
|
||||
make_divisional_loss_gl_entry(d, outgoing_amount)
|
||||
elif (
|
||||
d.item_code not in stock_items
|
||||
and not d.is_fixed_asset
|
||||
and flt(d.qty)
|
||||
and provisional_accounting_for_non_stock_items
|
||||
and d.get("provisional_expense_account")
|
||||
d.warehouse not in warehouse_with_no_account
|
||||
or d.rejected_warehouse not in warehouse_with_no_account
|
||||
):
|
||||
self.add_provisional_gl_entry(
|
||||
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
||||
)
|
||||
warehouse_with_no_account.append(d.warehouse)
|
||||
|
||||
if d.is_fixed_asset:
|
||||
self.update_assets(d, d.valuation_rate)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
frappe.msgprint(
|
||||
@@ -589,8 +621,8 @@ class PurchaseReceipt(BuyingController):
|
||||
self, item, gl_entries, posting_date, provisional_account, reverse=0
|
||||
):
|
||||
credit_currency = get_account_currency(provisional_account)
|
||||
debit_currency = get_account_currency(item.expense_account)
|
||||
expense_account = item.expense_account
|
||||
debit_currency = get_account_currency(item.expense_account)
|
||||
remarks = self.get("remarks") or _("Accounting Entry for Service")
|
||||
multiplication_factor = 1
|
||||
|
||||
@@ -631,11 +663,8 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
|
||||
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
|
||||
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")])
|
||||
is_asset_pr = any(d.is_fixed_asset for d in self.get("items"))
|
||||
# Cost center-wise amount breakup for other charges included for valuation
|
||||
valuation_tax = {}
|
||||
for tax in self.get("taxes"):
|
||||
@@ -655,26 +684,26 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
if negative_expense_to_be_booked and valuation_tax:
|
||||
# Backward compatibility:
|
||||
# If expenses_included_in_valuation account has been credited in against PI
|
||||
# and charges added via Landed Cost Voucher,
|
||||
# post valuation related charges on "Stock Received But Not Billed"
|
||||
# introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account
|
||||
|
||||
negative_expense_booked_in_pi = frappe.db.sql(
|
||||
"""select name from `tabPurchase Invoice Item` pi
|
||||
where docstatus = 1 and purchase_receipt=%s
|
||||
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice'
|
||||
and voucher_no=pi.parent and account=%s)""",
|
||||
(self.name, expenses_included_in_valuation),
|
||||
)
|
||||
|
||||
against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
|
||||
total_valuation_amount = sum(valuation_tax.values())
|
||||
amount_including_divisional_loss = negative_expense_to_be_booked
|
||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||
stock_rbnb = (
|
||||
self.get("asset_received_but_not_billed")
|
||||
if is_asset_pr
|
||||
else self.get_company_default("stock_received_but_not_billed")
|
||||
)
|
||||
i = 1
|
||||
for tax in self.get("taxes"):
|
||||
if valuation_tax.get(tax.name):
|
||||
negative_expense_booked_in_pi = frappe.db.sql(
|
||||
"""select name from `tabPurchase Invoice Item` pi
|
||||
where docstatus = 1 and purchase_receipt=%s
|
||||
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice'
|
||||
and voucher_no=pi.parent and account=%s)""",
|
||||
(self.name, tax.account_head),
|
||||
)
|
||||
|
||||
if negative_expense_booked_in_pi:
|
||||
account = stock_rbnb
|
||||
@@ -702,103 +731,6 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
i += 1
|
||||
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
if is_cwip_accounting_enabled(item.asset_category):
|
||||
self.add_asset_gl_entries(item, gl_entries)
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
self.add_lcv_gl_entries(item, gl_entries)
|
||||
# update assets gross amount by its valuation rate
|
||||
# valuation rate is total of net rate, raw mat supp cost, tax amount, lcv amount per item
|
||||
self.update_assets(item, item.valuation_rate)
|
||||
return gl_entries
|
||||
|
||||
def add_asset_gl_entries(self, item, gl_entries):
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
# This returns category's cwip account if not then fallback to company's default cwip account
|
||||
cwip_account = get_asset_account(
|
||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
||||
)
|
||||
|
||||
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
|
||||
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
||||
remarks = self.get("remarks") or _("Accounting Entry for Asset")
|
||||
|
||||
cwip_account_currency = get_account_currency(cwip_account)
|
||||
# debit cwip account
|
||||
debit_in_account_currency = (
|
||||
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=cwip_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=base_asset_amount,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=arbnb_account,
|
||||
debit_in_account_currency=debit_in_account_currency,
|
||||
item=item,
|
||||
)
|
||||
|
||||
asset_rbnb_currency = get_account_currency(arbnb_account)
|
||||
# credit arbnb account
|
||||
credit_in_account_currency = (
|
||||
base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=arbnb_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=base_asset_amount,
|
||||
remarks=remarks,
|
||||
against_account=cwip_account,
|
||||
credit_in_account_currency=credit_in_account_currency,
|
||||
item=item,
|
||||
)
|
||||
|
||||
def add_lcv_gl_entries(self, item, gl_entries):
|
||||
expenses_included_in_asset_valuation = self.get_company_default(
|
||||
"expenses_included_in_asset_valuation"
|
||||
)
|
||||
if not is_cwip_accounting_enabled(item.asset_category):
|
||||
asset_account = get_asset_category_account(
|
||||
asset_category=item.asset_category, fieldname="fixed_asset_account", company=self.company
|
||||
)
|
||||
else:
|
||||
# This returns company's default cwip account
|
||||
asset_account = get_asset_account("capital_work_in_progress_account", company=self.company)
|
||||
|
||||
remarks = self.get("remarks") or _("Accounting Entry for Stock")
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=expenses_included_in_asset_valuation,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=flt(item.landed_cost_voucher_amount),
|
||||
remarks=remarks,
|
||||
against_account=asset_account,
|
||||
project=item.project,
|
||||
item=item,
|
||||
)
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
account=asset_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=flt(item.landed_cost_voucher_amount),
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=expenses_included_in_asset_valuation,
|
||||
project=item.project,
|
||||
item=item,
|
||||
)
|
||||
|
||||
def update_assets(self, item, valuation_rate):
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code}
|
||||
@@ -823,16 +755,28 @@ class PurchaseReceipt(BuyingController):
|
||||
po_details.append(d.purchase_order_item)
|
||||
|
||||
if po_details:
|
||||
updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
|
||||
updated_pr += update_billed_amount_based_on_po(po_details, update_modified, self)
|
||||
|
||||
for pr in set(updated_pr):
|
||||
pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
|
||||
update_billing_percentage(pr_doc, update_modified=update_modified)
|
||||
|
||||
self.load_from_db()
|
||||
|
||||
def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse):
|
||||
return frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": voucher_no,
|
||||
"voucher_detail_no": voucher_detail_no,
|
||||
"warehouse": warehouse,
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
"stock_value_difference",
|
||||
)
|
||||
|
||||
|
||||
def update_billed_amount_based_on_po(po_details, update_modified=True):
|
||||
def update_billed_amount_based_on_po(po_details, update_modified=True, pr_doc=None):
|
||||
po_billed_amt_details = get_billed_amount_against_po(po_details)
|
||||
|
||||
# Get all Purchase Receipt Item rows against the Purchase Order Items
|
||||
@@ -861,13 +805,19 @@ def update_billed_amount_based_on_po(po_details, update_modified=True):
|
||||
po_billed_amt_details[pr_item.purchase_order_item] = billed_against_po
|
||||
|
||||
if pr_item.billed_amt != billed_amt_agianst_pr:
|
||||
frappe.db.set_value(
|
||||
"Purchase Receipt Item",
|
||||
pr_item.name,
|
||||
"billed_amt",
|
||||
billed_amt_agianst_pr,
|
||||
update_modified=update_modified,
|
||||
)
|
||||
# update existing doc if possible
|
||||
if pr_doc and pr_item.parent == pr_doc.name:
|
||||
pr_item = next((item for item in pr_doc.items if item.name == pr_item.name), None)
|
||||
pr_item.db_set("billed_amt", billed_amt_agianst_pr, update_modified=update_modified)
|
||||
|
||||
else:
|
||||
frappe.db.set_value(
|
||||
"Purchase Receipt Item",
|
||||
pr_item.name,
|
||||
"billed_amt",
|
||||
billed_amt_agianst_pr,
|
||||
update_modified=update_modified,
|
||||
)
|
||||
|
||||
updated_pr.append(pr_item.parent)
|
||||
|
||||
@@ -943,9 +893,6 @@ def get_billed_amount_against_po(po_items):
|
||||
|
||||
|
||||
def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False):
|
||||
# Reload as billed amount was set in db directly
|
||||
pr_doc.load_from_db()
|
||||
|
||||
# Update Billing % based on pending accepted qty
|
||||
total_amount, total_billed_amount = 0, 0
|
||||
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
|
||||
@@ -971,7 +918,6 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
||||
|
||||
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
|
||||
pr_doc.db_set("per_billed", percent_billed)
|
||||
pr_doc.load_from_db()
|
||||
|
||||
if update_modified:
|
||||
pr_doc.set_status(update=True)
|
||||
@@ -1093,6 +1039,7 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
"is_fixed_asset": "is_fixed_asset",
|
||||
"asset_location": "asset_location",
|
||||
"asset_category": "asset_category",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"filter": lambda d: get_pending_qty(d)[0] <= 0
|
||||
@@ -1250,3 +1197,8 @@ def get_item_account_wise_additional_cost(purchase_document):
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"])
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def update_regional_gl_entries(gl_list, doc):
|
||||
return
|
||||
|
||||
@@ -928,17 +928,33 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pr1.cancel()
|
||||
|
||||
def test_stock_transfer_from_purchase_receipt(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
|
||||
customer = "_Test Internal Customer 2"
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
pr1 = make_purchase_receipt(
|
||||
warehouse="Work In Progress - TCP1", company="_Test Company with perpetual inventory"
|
||||
warehouse="Stores - TCP1", company="_Test Company with perpetual inventory"
|
||||
)
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
|
||||
dn1 = create_delivery_note(
|
||||
item_code=pr1.items[0].item_code,
|
||||
company=company,
|
||||
customer=customer,
|
||||
cost_center="Main - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
qty=5,
|
||||
rate=500,
|
||||
warehouse="Stores - TCP1",
|
||||
target_warehouse="Work In Progress - TCP1",
|
||||
)
|
||||
|
||||
pr.supplier_warehouse = ""
|
||||
pr = make_inter_company_purchase_receipt(dn1.name)
|
||||
pr.items[0].from_warehouse = "Work In Progress - TCP1"
|
||||
|
||||
pr.items[0].warehouse = "Stores - TCP1"
|
||||
pr.submit()
|
||||
|
||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||
@@ -955,6 +971,11 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pr1.cancel()
|
||||
|
||||
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
|
||||
create_warehouse(
|
||||
"_Test Warehouse for Valuation",
|
||||
company="_Test Company with perpetual inventory",
|
||||
@@ -962,16 +983,28 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
)
|
||||
|
||||
pr1 = make_purchase_receipt(
|
||||
warehouse="_Test Warehouse for Valuation - TCP1",
|
||||
warehouse="Stores - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
|
||||
customer = "_Test Internal Customer 2"
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
dn1 = create_delivery_note(
|
||||
item_code=pr1.items[0].item_code,
|
||||
company=company,
|
||||
customer=customer,
|
||||
cost_center="Main - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
qty=5,
|
||||
rate=50,
|
||||
warehouse="Stores - TCP1",
|
||||
target_warehouse="_Test Warehouse for Valuation - TCP1",
|
||||
)
|
||||
|
||||
pr = make_inter_company_purchase_receipt(dn1.name)
|
||||
pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1"
|
||||
pr.supplier_warehouse = ""
|
||||
pr.items[0].warehouse = "Stores - TCP1"
|
||||
|
||||
pr.append(
|
||||
"taxes",
|
||||
@@ -2203,6 +2236,21 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
for entry in gl_entries:
|
||||
self.assertEqual(abs(entry.debit + entry.credit), abs(sl_entries[0].stock_value_difference))
|
||||
|
||||
def non_internal_transfer_purchase_receipt(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
pr_doc = make_purchase_receipt(do_not_submit=True)
|
||||
warehouse = create_warehouse("Internal Transfer Warehouse", pr_doc.company)
|
||||
pr_doc.items[0].db_set("target_warehouse", "warehouse")
|
||||
|
||||
pr_doc.reload()
|
||||
|
||||
self.assertEqual(pr_doc.items[0].from_warehouse, warehouse.name)
|
||||
|
||||
pr_doc.save()
|
||||
pr_doc.reload()
|
||||
self.assertFalse(pr_doc.items[0].from_warehouse)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -118,7 +118,9 @@
|
||||
"dimension_col_break",
|
||||
"cost_center",
|
||||
"section_break_80",
|
||||
"page_break"
|
||||
"page_break",
|
||||
"sales_order",
|
||||
"sales_order_item"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -921,7 +923,8 @@
|
||||
"label": "Delivery Note Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@@ -1025,12 +1028,32 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Order",
|
||||
"no_copy": 1,
|
||||
"options": "Sales Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_order_item",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Sales Order Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-03 21:11:50.547261",
|
||||
"modified": "2023-10-30 17:32:24.560337",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -191,7 +191,7 @@ class SerialNo(StockController):
|
||||
sle_dict = self.get_stock_ledger_entries(serial_no)
|
||||
if sle_dict:
|
||||
if sle_dict.get("incoming", []):
|
||||
entries["purchase_sle"] = sle_dict["incoming"][0]
|
||||
entries["purchase_sle"] = sle_dict["incoming"][-1]
|
||||
|
||||
if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0:
|
||||
entries["last_sle"] = sle_dict["incoming"][0]
|
||||
|
||||
@@ -451,31 +451,37 @@ class StockEntry(StockController):
|
||||
item_code.append(item.item_code)
|
||||
|
||||
def validate_fg_completed_qty(self):
|
||||
item_wise_qty = {}
|
||||
if self.purpose == "Manufacture" and self.work_order:
|
||||
for d in self.items:
|
||||
if d.is_finished_item:
|
||||
if self.process_loss_qty:
|
||||
d.qty = self.fg_completed_qty - self.process_loss_qty
|
||||
if self.purpose != "Manufacture":
|
||||
return
|
||||
|
||||
item_wise_qty.setdefault(d.item_code, []).append(d.qty)
|
||||
fg_qty = defaultdict(float)
|
||||
for d in self.items:
|
||||
if d.is_finished_item:
|
||||
fg_qty[d.item_code] += flt(d.qty)
|
||||
|
||||
if not fg_qty:
|
||||
return
|
||||
|
||||
precision = frappe.get_precision("Stock Entry Detail", "qty")
|
||||
for item_code, qty_list in item_wise_qty.items():
|
||||
total = flt(sum(qty_list), precision)
|
||||
fg_item = list(fg_qty.keys())[0]
|
||||
fg_item_qty = flt(fg_qty[fg_item], precision)
|
||||
fg_completed_qty = flt(self.fg_completed_qty, precision)
|
||||
|
||||
if (self.fg_completed_qty - total) > 0 and not self.process_loss_qty:
|
||||
self.process_loss_qty = flt(self.fg_completed_qty - total, precision)
|
||||
self.process_loss_percentage = flt(self.process_loss_qty * 100 / self.fg_completed_qty)
|
||||
for d in self.items:
|
||||
if not fg_qty.get(d.item_code):
|
||||
continue
|
||||
|
||||
if self.process_loss_qty:
|
||||
total += flt(self.process_loss_qty, precision)
|
||||
if (fg_completed_qty - fg_item_qty) > 0:
|
||||
self.process_loss_qty = fg_completed_qty - fg_item_qty
|
||||
|
||||
if self.fg_completed_qty != total:
|
||||
if not self.process_loss_qty:
|
||||
continue
|
||||
|
||||
if fg_completed_qty != (flt(fg_item_qty) + flt(self.process_loss_qty, precision)):
|
||||
frappe.throw(
|
||||
_("The finished product {0} quantity {1} and For Quantity {2} cannot be different").format(
|
||||
frappe.bold(item_code), frappe.bold(total), frappe.bold(self.fg_completed_qty)
|
||||
)
|
||||
_(
|
||||
"Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table."
|
||||
).format(frappe.bold(self.process_loss_qty), frappe.bold(d.item_code))
|
||||
)
|
||||
|
||||
def validate_difference_account(self):
|
||||
@@ -982,14 +988,34 @@ class StockEntry(StockController):
|
||||
& (se.docstatus == 1)
|
||||
& (se_detail.item_code == se_item.item_code)
|
||||
& (
|
||||
(se.purchase_order == self.purchase_order)
|
||||
((se.purchase_order == self.purchase_order) & (se_detail.po_detail == se_item.po_detail))
|
||||
if self.subcontract_data.order_doctype == "Purchase Order"
|
||||
else (se.subcontracting_order == self.subcontracting_order)
|
||||
else (
|
||||
(se.subcontracting_order == self.subcontracting_order)
|
||||
& (se_detail.sco_rm_detail == se_item.sco_rm_detail)
|
||||
)
|
||||
)
|
||||
)
|
||||
).run()[0][0]
|
||||
).run()[0][0] or 0
|
||||
|
||||
if flt(total_supplied, precision) > flt(total_allowed, precision):
|
||||
total_returned = 0
|
||||
if self.subcontract_data.order_doctype == "Subcontracting Order":
|
||||
total_returned = (
|
||||
frappe.qb.from_(se)
|
||||
.inner_join(se_detail)
|
||||
.on(se.name == se_detail.parent)
|
||||
.select(Sum(se_detail.transfer_qty))
|
||||
.where(
|
||||
(se.purpose == "Material Transfer")
|
||||
& (se.docstatus == 1)
|
||||
& (se.is_return == 1)
|
||||
& (se_detail.item_code == se_item.item_code)
|
||||
& (se_detail.sco_rm_detail == se_item.sco_rm_detail)
|
||||
& (se.subcontracting_order == self.subcontracting_order)
|
||||
)
|
||||
).run()[0][0] or 0
|
||||
|
||||
if flt(total_supplied - total_returned, precision) > flt(total_allowed, precision):
|
||||
frappe.throw(
|
||||
_("Row {0}# Item {1} cannot be transferred more than {2} against {3} {4}").format(
|
||||
se_item.idx,
|
||||
|
||||
@@ -447,9 +447,7 @@ class TestStockEntry(FrappeTestCase):
|
||||
repack.posting_date = nowdate()
|
||||
repack.posting_time = nowtime()
|
||||
|
||||
expenses_included_in_valuation = frappe.get_value(
|
||||
"Company", company, "expenses_included_in_valuation"
|
||||
)
|
||||
default_expense_account = frappe.get_value("Company", company, "default_expense_account")
|
||||
|
||||
items = get_multiple_items()
|
||||
repack.items = []
|
||||
@@ -460,12 +458,12 @@ class TestStockEntry(FrappeTestCase):
|
||||
"additional_costs",
|
||||
[
|
||||
{
|
||||
"expense_account": expenses_included_in_valuation,
|
||||
"expense_account": default_expense_account,
|
||||
"description": "Actual Operating Cost",
|
||||
"amount": 1000,
|
||||
},
|
||||
{
|
||||
"expense_account": expenses_included_in_valuation,
|
||||
"expense_account": default_expense_account,
|
||||
"description": "Additional Operating Cost",
|
||||
"amount": 200,
|
||||
},
|
||||
@@ -504,9 +502,7 @@ class TestStockEntry(FrappeTestCase):
|
||||
self.check_gl_entries(
|
||||
"Stock Entry",
|
||||
repack.name,
|
||||
sorted(
|
||||
[[stock_in_hand_account, 1200, 0.0], ["Expenses Included In Valuation - TCP1", 0.0, 1200.0]]
|
||||
),
|
||||
sorted([[stock_in_hand_account, 1200, 0.0], ["Cost of Goods Sold - TCP1", 0.0, 1200.0]]),
|
||||
)
|
||||
|
||||
def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):
|
||||
|
||||
@@ -95,13 +95,6 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
fieldname: "item_code",
|
||||
fieldtype: "Link",
|
||||
options: "Item",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"disabled": 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __("Ignore Empty Stock"),
|
||||
|
||||
@@ -647,7 +647,7 @@ class StockReconciliation(StockController):
|
||||
{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
|
||||
"name",
|
||||
)
|
||||
and current_qty
|
||||
and current_qty > 0
|
||||
):
|
||||
new_sle = self.get_sle_for_items(row)
|
||||
new_sle.actual_qty = current_qty * -1
|
||||
|
||||
@@ -302,7 +302,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
|
||||
if not item:
|
||||
item = frappe.get_doc("Item", args.get("item_code"))
|
||||
|
||||
if item.variant_of:
|
||||
if item.variant_of and not item.taxes:
|
||||
item.update_template_tables()
|
||||
|
||||
item_defaults = get_item_defaults(item.name, args.company)
|
||||
@@ -364,8 +364,12 @@ def get_basic_details(args, item, overwrite_warehouse=True):
|
||||
),
|
||||
"expense_account": expense_account
|
||||
or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults),
|
||||
"discount_account": get_default_discount_account(args, item_defaults),
|
||||
"provisional_expense_account": get_provisional_account(args, item_defaults),
|
||||
"discount_account": get_default_discount_account(
|
||||
args, item_defaults, item_group_defaults, brand_defaults
|
||||
),
|
||||
"provisional_expense_account": get_provisional_account(
|
||||
args, item_defaults, item_group_defaults, brand_defaults
|
||||
),
|
||||
"cost_center": get_default_cost_center(
|
||||
args, item_defaults, item_group_defaults, brand_defaults
|
||||
),
|
||||
@@ -719,12 +723,22 @@ def get_default_expense_account(args, item, item_group, brand):
|
||||
)
|
||||
|
||||
|
||||
def get_provisional_account(args, item):
|
||||
return item.get("default_provisional_account") or args.default_provisional_account
|
||||
def get_provisional_account(args, item, item_group, brand):
|
||||
return (
|
||||
item.get("default_provisional_account")
|
||||
or item_group.get("default_provisional_account")
|
||||
or brand.get("default_provisional_account")
|
||||
or args.default_provisional_account
|
||||
)
|
||||
|
||||
|
||||
def get_default_discount_account(args, item):
|
||||
return item.get("default_discount_account") or args.discount_account
|
||||
def get_default_discount_account(args, item, item_group, brand):
|
||||
return (
|
||||
item.get("default_discount_account")
|
||||
or item_group.get("default_discount_account")
|
||||
or brand.get("default_discount_account")
|
||||
or args.discount_account
|
||||
)
|
||||
|
||||
|
||||
def get_default_deferred_account(args, item, fieldname=None):
|
||||
@@ -771,6 +785,12 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan
|
||||
data = frappe.get_attr(path)(args.get("item_code"), company)
|
||||
|
||||
if data and (data.selling_cost_center or data.buying_cost_center):
|
||||
if args.get("customer") and data.selling_cost_center:
|
||||
return data.selling_cost_center
|
||||
|
||||
elif args.get("supplier") and data.buying_cost_center:
|
||||
return data.buying_cost_center
|
||||
|
||||
return data.selling_cost_center or data.buying_cost_center
|
||||
|
||||
if not cost_center and args.get("cost_center"):
|
||||
|
||||
@@ -107,7 +107,7 @@ frappe.ui.form.on('Subcontracting Order', {
|
||||
get_materials_from_supplier: function (frm) {
|
||||
let sco_rm_details = [];
|
||||
|
||||
if (frm.doc.status != "Closed" && frm.doc.supplied_items && frm.doc.per_received > 0) {
|
||||
if (frm.doc.status != "Closed" && frm.doc.supplied_items) {
|
||||
frm.doc.supplied_items.forEach(d => {
|
||||
if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) {
|
||||
sco_rm_details.push(d.name);
|
||||
@@ -193,7 +193,7 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
|
||||
}
|
||||
|
||||
has_unsupplied_items() {
|
||||
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
|
||||
return this.frm.doc['supplied_items'].some(item => item.required_qty > (item.supplied_qty - item.returned_qty));
|
||||
}
|
||||
|
||||
make_subcontracting_receipt() {
|
||||
|
||||
@@ -299,7 +299,6 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
|
||||
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
|
||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
|
||||
warehouse_with_no_account = []
|
||||
|
||||
@@ -371,10 +370,7 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
divisional_loss = flt(item.amount - stock_value_diff, item.precision("amount"))
|
||||
|
||||
if divisional_loss:
|
||||
if self.is_return:
|
||||
loss_account = expenses_included_in_valuation
|
||||
else:
|
||||
loss_account = item.expense_account
|
||||
loss_account = item.expense_account
|
||||
|
||||
self.add_gl_entry(
|
||||
gl_entries=gl_entries,
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
frappe.ui.form.on("Issue", {
|
||||
onload: function(frm) {
|
||||
frm.email_field = "raised_by";
|
||||
frm.set_query("customer", function () {
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frappe.db.get_value("Support Settings", {name: "Support Settings"},
|
||||
["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
|
||||
|
||||
@@ -369,7 +369,7 @@ Base,Basis,
|
||||
Base URL,Basis-URL,
|
||||
Based On,Basiert auf,
|
||||
Based On Payment Terms,Basierend auf Zahlungsbedingungen,
|
||||
Basic,Grundeinkommen,
|
||||
Basic,Basic,
|
||||
Batch,Charge,
|
||||
Batch Entries,Batch-Einträge,
|
||||
Batch ID is mandatory,Batch-ID ist obligatorisch,
|
||||
|
||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user