mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-16 19:49:18 +00:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7235c3f88f | ||
|
|
15733c14e8 | ||
|
|
22b2386aa1 | ||
|
|
6ab0637b0b | ||
|
|
76ae4d87ca | ||
|
|
3c688dfa6d | ||
|
|
8b8d054ded | ||
|
|
121ec83562 | ||
|
|
26536da74b | ||
|
|
f605564094 | ||
|
|
b7e8fbe43f | ||
|
|
00a73c7a57 | ||
|
|
edb100274b | ||
|
|
6976316ada | ||
|
|
357f74a580 | ||
|
|
aedd0397b8 | ||
|
|
4cb685a326 | ||
|
|
afcc0cbccd | ||
|
|
29bca45b1e | ||
|
|
784fb47197 | ||
|
|
5520c6b2f3 | ||
|
|
f873547447 | ||
|
|
d65d2f617d | ||
|
|
0438433a4f | ||
|
|
055556b7f1 | ||
|
|
60fa421409 | ||
|
|
dd2fd12d5f | ||
|
|
319ee41403 | ||
|
|
b96526eefd | ||
|
|
78a992c086 | ||
|
|
f871dd4ef6 | ||
|
|
79ecf7751f | ||
|
|
4698dba402 | ||
|
|
f55881aef8 | ||
|
|
f1b2ba5a84 | ||
|
|
4409f11282 | ||
|
|
84865a8421 | ||
|
|
d9632e8138 | ||
|
|
4bbd0ec985 | ||
|
|
92f8f0ec74 | ||
|
|
aea484be1f | ||
|
|
52fc10d00c | ||
|
|
1b9082e07b | ||
|
|
0e9a1fb40e | ||
|
|
32abf67c80 | ||
|
|
52b42e9492 | ||
|
|
77ac7f06d4 | ||
|
|
8717235a34 | ||
|
|
670426f428 | ||
|
|
1b5a1cbaad | ||
|
|
7ca1beb15d | ||
|
|
c660db145b | ||
|
|
0aaf9c4f05 | ||
|
|
b3b0272ec9 | ||
|
|
f7898b4954 | ||
|
|
b067eae38c | ||
|
|
cee867f941 | ||
|
|
6c4fcd80c6 | ||
|
|
11657effa5 | ||
|
|
d3b7942f32 | ||
|
|
f407c972d1 | ||
|
|
0b72295fa2 | ||
|
|
204b6c0272 | ||
|
|
1118e25b6d | ||
|
|
9b9df70632 | ||
|
|
39ff0cc6d8 | ||
|
|
c407d1e51a | ||
|
|
4d8ced6c87 | ||
|
|
4f832678cf | ||
|
|
f0f7afa669 | ||
|
|
cb6cbf7818 | ||
|
|
3aec1175df | ||
|
|
c645995ae3 | ||
|
|
f772c17f3f | ||
|
|
193502ce03 | ||
|
|
abd637a238 | ||
|
|
4cf9fb08e1 | ||
|
|
e4ca20654f | ||
|
|
bc873939eb | ||
|
|
c6d3e9f432 | ||
|
|
54f4504df6 | ||
|
|
309da96442 | ||
|
|
94ebfa765c | ||
|
|
2577747c5c | ||
|
|
fb387426d6 | ||
|
|
15915d7053 | ||
|
|
362976fa42 | ||
|
|
a7d23abc2f | ||
|
|
4b609322ba | ||
|
|
5778f227ee | ||
|
|
7ac75aab1a | ||
|
|
e831b6e054 | ||
|
|
856a64b77c | ||
|
|
3afb625ff8 | ||
|
|
b6d2de2cc1 | ||
|
|
0bfb774bdf | ||
|
|
d7a8db04a1 | ||
|
|
2d5ae811d2 | ||
|
|
be9607e27b | ||
|
|
fd45a7afbe | ||
|
|
0c73af6ee2 | ||
|
|
981add9b6f | ||
|
|
8f5736c500 | ||
|
|
052f7c3345 | ||
|
|
fa4a40812c | ||
|
|
a76732613e | ||
|
|
dd602989a8 | ||
|
|
78b39d6ca4 | ||
|
|
77fa64e100 | ||
|
|
ac04fc60ef | ||
|
|
6be77d5729 | ||
|
|
37e5b93e2d | ||
|
|
19d29d1861 | ||
|
|
0db912998a | ||
|
|
200a971743 | ||
|
|
c0f0986539 | ||
|
|
376293326b | ||
|
|
9a29e3c9f2 | ||
|
|
e099e10c8e | ||
|
|
3b222339b8 | ||
|
|
9e60dd32e8 | ||
|
|
7ff5414571 | ||
|
|
d48487ada2 | ||
|
|
da69cc5477 | ||
|
|
b2a720d847 | ||
|
|
6656d23e45 | ||
|
|
ae5c05081d | ||
|
|
028e939cca | ||
|
|
216cb9b07b | ||
|
|
50ad612453 | ||
|
|
6b71af9008 | ||
|
|
3c8412efdb | ||
|
|
623f56a95c | ||
|
|
b637d4d5f1 | ||
|
|
601bc64618 | ||
|
|
04d3571dd9 | ||
|
|
aa5aaa113e | ||
|
|
354a9d6169 | ||
|
|
c7e2217c92 | ||
|
|
ce5fc5b457 | ||
|
|
abe18945a6 | ||
|
|
010a0ca0a9 | ||
|
|
74664a34c0 | ||
|
|
82f1dd268d | ||
|
|
1aa96defda | ||
|
|
8fdbbf374d | ||
|
|
66e5202642 | ||
|
|
35e9bfca38 | ||
|
|
735a60807a | ||
|
|
c3fd802351 | ||
|
|
6046f8bc5e | ||
|
|
d7db8ed12e | ||
|
|
adcd21724b | ||
|
|
04bdff736b | ||
|
|
65bb1d8cc2 | ||
|
|
ebf766cf62 | ||
|
|
80bf47170f | ||
|
|
0faa7b0432 | ||
|
|
1d1f12f949 | ||
|
|
4c82533239 | ||
|
|
5a28ba8537 | ||
|
|
8737c10ce4 | ||
|
|
2defb89962 | ||
|
|
53b9d61c46 | ||
|
|
3092131913 | ||
|
|
6dce122825 | ||
|
|
248cc48842 | ||
|
|
ebd8f2f45b |
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -2,7 +2,7 @@ name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-13
|
||||
- version-14
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
@@ -13,10 +13,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js v14
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 16
|
||||
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||
@@ -28,4 +30,4 @@ jobs:
|
||||
GIT_AUTHOR_EMAIL: "developers@frappe.io"
|
||||
GIT_COMMITTER_NAME: "Frappe PR Bot"
|
||||
GIT_COMMITTER_EMAIL: "developers@frappe.io"
|
||||
run: npx semantic-release
|
||||
run: npx semantic-release
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"branches": ["version-13"],
|
||||
"branches": ["version-14"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
@@ -10,7 +10,7 @@
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec", {
|
||||
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
|
||||
"prepareCmd": 'sed -ir -E "s/\"[0-9]+\.[0-9]+\.[0-9]+\"/\"${nextRelease.version}\"/" erpnext/__init__.py'
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -21,4 +21,4 @@
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.0.0-dev"
|
||||
__version__ = "14.1.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -366,7 +366,7 @@ def update_outstanding_amt(
|
||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
|
||||
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
|
||||
|
||||
# Didn't use db_set for optimisation purpose
|
||||
# Didn't use db_set for optimization purpose
|
||||
ref_doc.outstanding_amount = bal
|
||||
frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Journal Entry Template", {
|
||||
setup: function(frm) {
|
||||
refresh: function(frm) {
|
||||
frappe.model.set_default_values(frm.doc);
|
||||
|
||||
frm.set_query("account" ,"accounts", function(){
|
||||
|
||||
@@ -181,7 +181,11 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("Party is mandatory"))
|
||||
|
||||
_party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name"
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
|
||||
|
||||
if frappe.db.has_column(self.party_type, _party_name):
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
|
||||
else:
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
|
||||
|
||||
if self.party:
|
||||
if not self.party_balance:
|
||||
@@ -295,6 +299,9 @@ class PaymentEntry(AccountsController):
|
||||
def validate_reference_documents(self):
|
||||
valid_reference_doctypes = self.get_valid_reference_doctypes()
|
||||
|
||||
if not valid_reference_doctypes:
|
||||
return
|
||||
|
||||
for d in self.get("references"):
|
||||
if not d.allocated_amount:
|
||||
continue
|
||||
@@ -362,7 +369,7 @@ class PaymentEntry(AccountsController):
|
||||
if not d.allocated_amount:
|
||||
continue
|
||||
|
||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
|
||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
outstanding_amount, is_return = frappe.get_cached_value(
|
||||
d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]
|
||||
)
|
||||
@@ -1184,6 +1191,7 @@ def get_outstanding_reference_documents(args):
|
||||
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
common_filter = []
|
||||
posting_and_due_date = []
|
||||
|
||||
# confirm that Supplier is not blocked
|
||||
if args.get("party_type") == "Supplier":
|
||||
@@ -1200,7 +1208,7 @@ def get_outstanding_reference_documents(args):
|
||||
party_account_currency = get_account_currency(args.get("party_account"))
|
||||
company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency")
|
||||
|
||||
# Get positive outstanding sales /purchase invoices/ Fees
|
||||
# Get positive outstanding sales /purchase invoices
|
||||
condition = ""
|
||||
if args.get("voucher_type") and args.get("voucher_no"):
|
||||
condition = " and voucher_type={0} and voucher_no={1}".format(
|
||||
@@ -1224,7 +1232,7 @@ def get_outstanding_reference_documents(args):
|
||||
condition += " and {0} between '{1}' and '{2}'".format(
|
||||
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
|
||||
)
|
||||
common_filter.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
|
||||
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
|
||||
|
||||
if args.get("company"):
|
||||
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
|
||||
@@ -1235,6 +1243,7 @@ def get_outstanding_reference_documents(args):
|
||||
args.get("party"),
|
||||
args.get("party_account"),
|
||||
common_filter=common_filter,
|
||||
posting_date=posting_and_due_date,
|
||||
min_outstanding=args.get("outstanding_amt_greater_than"),
|
||||
max_outstanding=args.get("outstanding_amt_less_than"),
|
||||
)
|
||||
@@ -1595,10 +1604,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
elif reference_doctype != "Journal Entry":
|
||||
if not total_amount:
|
||||
if party_account_currency == company_currency:
|
||||
total_amount = ref_doc.base_grand_total
|
||||
# for handling cases that don't have multi-currency (base field)
|
||||
total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total")
|
||||
exchange_rate = 1
|
||||
else:
|
||||
total_amount = ref_doc.grand_total
|
||||
total_amount = ref_doc.get("grand_total")
|
||||
if not exchange_rate:
|
||||
# Get the exchange rate from the original ref doc
|
||||
# or get it based on the posting date of the ref doc.
|
||||
@@ -1609,7 +1619,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
else:
|
||||
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
||||
outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
|
||||
|
||||
else:
|
||||
# Get the exchange rate based on the posting date of the ref doc.
|
||||
@@ -1627,16 +1637,23 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
|
||||
def get_payment_entry(
|
||||
dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
|
||||
):
|
||||
reference_doc = None
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||
|
||||
party_type = set_party_type(dt)
|
||||
if not party_type:
|
||||
party_type = set_party_type(dt)
|
||||
|
||||
party_account = set_party_account(dt, dn, doc, party_type)
|
||||
party_account_currency = set_party_account_currency(dt, party_account, doc)
|
||||
payment_type = set_payment_type(dt, doc)
|
||||
|
||||
if not payment_type:
|
||||
payment_type = set_payment_type(dt, doc)
|
||||
|
||||
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(
|
||||
party_amount, dt, party_account_currency, doc
|
||||
)
|
||||
@@ -1786,8 +1803,6 @@ def set_party_account(dt, dn, doc, party_type):
|
||||
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
|
||||
elif dt == "Purchase Invoice":
|
||||
party_account = doc.credit_to
|
||||
elif dt == "Fees":
|
||||
party_account = doc.receivable_account
|
||||
else:
|
||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||
return party_account
|
||||
@@ -1803,8 +1818,7 @@ def set_party_account_currency(dt, party_account, doc):
|
||||
|
||||
def set_payment_type(dt, doc):
|
||||
if (
|
||||
dt == "Sales Order"
|
||||
or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)
|
||||
dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
|
||||
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
|
||||
payment_type = "Receive"
|
||||
else:
|
||||
@@ -1822,18 +1836,15 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
|
||||
else:
|
||||
grand_total = doc.rounded_total or doc.grand_total
|
||||
outstanding_amount = doc.outstanding_amount
|
||||
elif dt == "Fees":
|
||||
grand_total = doc.grand_total
|
||||
outstanding_amount = doc.outstanding_amount
|
||||
elif dt == "Dunning":
|
||||
grand_total = doc.grand_total
|
||||
outstanding_amount = doc.grand_total
|
||||
else:
|
||||
if party_account_currency == doc.company_currency:
|
||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||
grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total"))
|
||||
else:
|
||||
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
|
||||
outstanding_amount = grand_total - flt(doc.advance_paid)
|
||||
grand_total = flt(doc.get("rounded_total") or doc.get("grand_total"))
|
||||
outstanding_amount = doc.get("outstanding_amount") or (grand_total - flt(doc.advance_paid))
|
||||
return grand_total, outstanding_amount
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"amount",
|
||||
"account_currency",
|
||||
"amount_in_account_currency",
|
||||
"delinked"
|
||||
"delinked",
|
||||
"remarks"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -136,12 +137,17 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"label": "Remarks"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-07-11 09:13:54.379168",
|
||||
"modified": "2022-08-22 15:32:56.629430",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Ledger Entry",
|
||||
|
||||
@@ -22,6 +22,7 @@ class PaymentReconciliation(Document):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PaymentReconciliation, self).__init__(*args, **kwargs)
|
||||
self.common_filter_conditions = []
|
||||
self.ple_posting_date_filter = []
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_unreconciled_entries(self):
|
||||
@@ -150,6 +151,7 @@ class PaymentReconciliation(Document):
|
||||
return_outstanding = ple_query.get_voucher_outstandings(
|
||||
vouchers=return_invoices,
|
||||
common_filter=self.common_filter_conditions,
|
||||
posting_date=self.ple_posting_date_filter,
|
||||
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
|
||||
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
|
||||
get_payments=True,
|
||||
@@ -187,6 +189,7 @@ class PaymentReconciliation(Document):
|
||||
self.party,
|
||||
self.receivable_payable_account,
|
||||
common_filter=self.common_filter_conditions,
|
||||
posting_date=self.ple_posting_date_filter,
|
||||
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
|
||||
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
|
||||
)
|
||||
@@ -350,6 +353,7 @@ class PaymentReconciliation(Document):
|
||||
|
||||
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
|
||||
self.common_filter_conditions.clear()
|
||||
self.ple_posting_date_filter.clear()
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
self.common_filter_conditions.append(ple.company == self.company)
|
||||
@@ -359,15 +363,15 @@ class PaymentReconciliation(Document):
|
||||
|
||||
if get_invoices:
|
||||
if self.from_invoice_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.gte(self.from_invoice_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_invoice_date))
|
||||
if self.to_invoice_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.lte(self.to_invoice_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_invoice_date))
|
||||
|
||||
elif get_return_invoices:
|
||||
if self.from_payment_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.gte(self.from_payment_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_payment_date))
|
||||
if self.to_payment_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.lte(self.to_payment_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
|
||||
|
||||
def get_conditions(self, get_payments=False):
|
||||
condition = " and company = '{0}' ".format(self.company)
|
||||
|
||||
@@ -283,6 +283,41 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(len(pr.get("invoices")), 2)
|
||||
self.assertEqual(len(pr.get("payments")), 2)
|
||||
|
||||
def test_filter_posting_date_case2(self):
|
||||
"""
|
||||
Posting date should not affect outstanding amount calculation
|
||||
"""
|
||||
|
||||
from_date = add_days(nowdate(), -30)
|
||||
to_date = nowdate()
|
||||
self.create_payment_entry(amount=25, posting_date=from_date).submit()
|
||||
self.create_sales_invoice(rate=25, qty=1, posting_date=to_date)
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.from_invoice_date = pr.from_payment_date = from_date
|
||||
pr.to_invoice_date = pr.to_payment_date = to_date
|
||||
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.reconcile()
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
pr.from_invoice_date = pr.from_payment_date = to_date
|
||||
pr.to_invoice_date = pr.to_payment_date = to_date
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
|
||||
def test_filter_invoice_limit(self):
|
||||
# check filter condition - invoice limit
|
||||
transaction_date = nowdate()
|
||||
|
||||
@@ -36,6 +36,15 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
});
|
||||
|
||||
set_html_data(frm);
|
||||
|
||||
if (frm.doc.docstatus == 1) {
|
||||
if (!frm.doc.posting_date) {
|
||||
frm.set_value("posting_date", frappe.datetime.nowdate());
|
||||
}
|
||||
if (!frm.doc.posting_time) {
|
||||
frm.set_value("posting_time", frappe.datetime.now_time());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"period_end_date",
|
||||
"column_break_3",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"pos_opening_entry",
|
||||
"status",
|
||||
"section_break_5",
|
||||
@@ -51,7 +52,6 @@
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
"label": "Period End Date",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -219,6 +219,13 @@
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Error",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
@@ -228,10 +235,11 @@
|
||||
"link_fieldname": "pos_closing_entry"
|
||||
}
|
||||
],
|
||||
"modified": "2021-10-20 16:19:25.340565",
|
||||
"modified": "2022-08-01 11:37:14.991228",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -278,5 +286,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -15,6 +15,9 @@ from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
class POSClosingEntry(StatusUpdater):
|
||||
def validate(self):
|
||||
self.posting_date = self.posting_date or frappe.utils.nowdate()
|
||||
self.posting_time = self.posting_time or frappe.utils.nowtime()
|
||||
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"merge_invoices_based_on",
|
||||
"column_break_3",
|
||||
"pos_closing_entry",
|
||||
@@ -105,12 +106,19 @@
|
||||
"label": "Customer Group",
|
||||
"mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
|
||||
"options": "Customer Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-14 11:17:19.001142",
|
||||
"modified": "2022-08-01 11:36:42.456429",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Merge Log",
|
||||
@@ -173,5 +181,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -9,7 +9,7 @@ from frappe import _
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc, map_doc
|
||||
from frappe.utils import cint, flt, getdate, nowdate
|
||||
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
@@ -99,6 +99,7 @@ class POSInvoiceMergeLog(Document):
|
||||
sales_invoice.is_consolidated = 1
|
||||
sales_invoice.set_posting_time = 1
|
||||
sales_invoice.posting_date = getdate(self.posting_date)
|
||||
sales_invoice.posting_time = get_time(self.posting_time)
|
||||
sales_invoice.save()
|
||||
sales_invoice.submit()
|
||||
|
||||
@@ -115,6 +116,7 @@ class POSInvoiceMergeLog(Document):
|
||||
credit_note.is_consolidated = 1
|
||||
credit_note.set_posting_time = 1
|
||||
credit_note.posting_date = getdate(self.posting_date)
|
||||
credit_note.posting_time = get_time(self.posting_time)
|
||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||
# credit_note.return_against = self.consolidated_invoice
|
||||
credit_note.save()
|
||||
@@ -402,6 +404,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
merge_log.posting_date = (
|
||||
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
|
||||
)
|
||||
merge_log.posting_time = (
|
||||
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
|
||||
)
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"currency",
|
||||
"write_off_account",
|
||||
"write_off_cost_center",
|
||||
"write_off_limit",
|
||||
"account_for_change_amount",
|
||||
"disable_rounded_total",
|
||||
"column_break_23",
|
||||
@@ -360,6 +361,14 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Validate Stock on Save"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Auto write off precision loss while consolidation",
|
||||
"fieldname": "write_off_limit",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Write Off Limit",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, the consolidated invoices will have rounded total disabled",
|
||||
@@ -393,7 +402,7 @@
|
||||
"link_fieldname": "pos_profile"
|
||||
}
|
||||
],
|
||||
"modified": "2022-07-21 11:16:46.911173",
|
||||
"modified": "2022-08-10 12:57:06.241439",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
||||
@@ -34,4 +34,4 @@ class ProcessDeferredAccounting(Document):
|
||||
filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
|
||||
)
|
||||
|
||||
make_gl_entries(gl_entries=gl_entries, cancel=1)
|
||||
make_gl_entries(gl_map=gl_entries, cancel=1)
|
||||
|
||||
@@ -57,3 +57,16 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, "2019-01-10")
|
||||
|
||||
def test_pda_submission_and_cancellation(self):
|
||||
pda = frappe.get_doc(
|
||||
dict(
|
||||
doctype="Process Deferred Accounting",
|
||||
posting_date="2019-01-01",
|
||||
start_date="2019-01-01",
|
||||
end_date="2019-01-31",
|
||||
type="Income",
|
||||
)
|
||||
)
|
||||
pda.submit()
|
||||
pda.cancel()
|
||||
|
||||
@@ -575,7 +575,6 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.make_supplier_gl_entry(gl_entries)
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_discount_gl_entries(gl_entries)
|
||||
|
||||
if self.check_asset_cwip_enabled():
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
@@ -807,7 +806,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
|
||||
dummy, amount = self.get_amount_and_base_amount(item, None)
|
||||
else:
|
||||
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
|
||||
|
||||
@@ -1165,7 +1164,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
|
||||
amount, base_amount = self.get_tax_amounts(tax, None)
|
||||
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
|
||||
@@ -1791,4 +1790,6 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
target_doc,
|
||||
)
|
||||
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -338,59 +338,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
|
||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
||||
|
||||
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||
def test_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
|
||||
discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 225.0, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 25.0, nowdate()],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
|
||||
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
|
||||
additional_discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
|
||||
pi.apply_discount_on = "Grand Total"
|
||||
pi.additional_discount_account = additional_discount_account
|
||||
pi.additional_discount_percentage = 10
|
||||
pi.disable_rounded_total = 1
|
||||
pi.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "Test",
|
||||
"rate": 10,
|
||||
},
|
||||
)
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
|
||||
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 247.5, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 27.5, nowdate()],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
|
||||
def test_purchase_invoice_change_naming_series(self):
|
||||
pi = frappe.copy_doc(test_records[1])
|
||||
pi.insert()
|
||||
|
||||
@@ -479,9 +479,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
|
||||
is_cash_or_non_trade_discount() {
|
||||
this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
|
||||
this.frm.set_df_property("additional_discount_account", "reqd", this.frm.doc.is_cash_or_non_trade_discount);
|
||||
|
||||
if (!this.frm.doc.is_cash_or_non_trade_discount) {
|
||||
this.frm.set_value("additional_discount_account", "");
|
||||
}
|
||||
|
||||
this.calculate_taxes_and_totals();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1033,22 +1033,6 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
)
|
||||
|
||||
if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against": self.debit_to,
|
||||
"debit": self.base_discount_amount,
|
||||
"debit_in_account_currency": self.discount_amount,
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project,
|
||||
},
|
||||
self.currency,
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||
@@ -2103,13 +2087,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
|
||||
source_document_warehouse_field = "target_warehouse"
|
||||
target_document_warehouse_field = "from_warehouse"
|
||||
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||
else:
|
||||
source_doc = frappe.get_doc(doctype, source_name)
|
||||
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
|
||||
source_document_warehouse_field = "from_warehouse"
|
||||
target_document_warehouse_field = "target_warehouse"
|
||||
|
||||
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||
received_items = {}
|
||||
|
||||
validate_inter_company_transaction(source_doc, doctype)
|
||||
details = get_inter_company_details(source_doc, doctype)
|
||||
|
||||
@@ -282,7 +282,6 @@
|
||||
"label": "Discount (%) on Price List Rate with Margin",
|
||||
"oldfieldname": "adj_rate",
|
||||
"oldfieldtype": "Float",
|
||||
"precision": "2",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -846,7 +845,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-17 05:33:15.335912",
|
||||
"modified": "2022-08-26 12:06:31.205417",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -318,7 +318,6 @@ def get_advance_vouchers(
|
||||
"is_cancelled": 0,
|
||||
"party_type": party_type,
|
||||
"party": ["in", parties],
|
||||
"against_voucher": ["is", "not set"],
|
||||
}
|
||||
|
||||
if company:
|
||||
|
||||
@@ -207,7 +207,7 @@ def set_address_details(
|
||||
)
|
||||
|
||||
if company_address:
|
||||
party_details.update({"company_address": company_address})
|
||||
party_details.company_address = company_address
|
||||
else:
|
||||
party_details.update(get_company_address(company))
|
||||
|
||||
@@ -219,12 +219,31 @@ def set_address_details(
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
|
||||
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
|
||||
if party_details.company_address:
|
||||
party_details["shipping_address"] = shipping_address or party_details["company_address"]
|
||||
party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
|
||||
if shipping_address:
|
||||
party_details.update(
|
||||
get_fetch_values(doctype, "shipping_address", party_details.shipping_address)
|
||||
shipping_address=shipping_address,
|
||||
shipping_address_display=get_address_display(shipping_address),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||
)
|
||||
|
||||
if party_details.company_address:
|
||||
# billing address
|
||||
party_details.update(
|
||||
billing_address=party_details.company_address,
|
||||
billing_address_display=(
|
||||
party_details.company_address_display or get_address_display(party_details.company_address)
|
||||
),
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
||||
)
|
||||
|
||||
# shipping address - if not already set
|
||||
if not party_details.shipping_address:
|
||||
party_details.update(
|
||||
shipping_address=party_details.billing_address,
|
||||
shipping_address_display=party_details.billing_address_display,
|
||||
**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
|
||||
)
|
||||
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
|
||||
return party_details.get(billing_address_field), party_details.shipping_address_name
|
||||
|
||||
@@ -178,6 +178,11 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_name",
|
||||
"label": __("Customer Name"),
|
||||
|
||||
@@ -119,6 +119,7 @@ class ReceivablePayableReport(object):
|
||||
party_account=ple.account,
|
||||
posting_date=ple.posting_date,
|
||||
account_currency=ple.account_currency,
|
||||
remarks=ple.remarks,
|
||||
invoiced=0.0,
|
||||
paid=0.0,
|
||||
credit_note=0.0,
|
||||
@@ -165,7 +166,7 @@ class ReceivablePayableReport(object):
|
||||
"range4",
|
||||
"range5",
|
||||
"future_amount",
|
||||
"remaining_balance"
|
||||
"remaining_balance",
|
||||
]
|
||||
|
||||
def get_voucher_balance(self, ple):
|
||||
@@ -178,6 +179,11 @@ class ReceivablePayableReport(object):
|
||||
|
||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
row = self.voucher_balance.get(key)
|
||||
|
||||
if not row:
|
||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||
|
||||
return row
|
||||
|
||||
def update_voucher_balance(self, ple):
|
||||
@@ -187,7 +193,11 @@ class ReceivablePayableReport(object):
|
||||
if not row:
|
||||
return
|
||||
|
||||
amount = ple.amount
|
||||
# amount in "Party Currency", if its supplied. If not, amount in company currency
|
||||
if self.filters.get(scrub(self.party_type)):
|
||||
amount = ple.amount_in_account_currency
|
||||
else:
|
||||
amount = ple.amount
|
||||
amount_in_account_currency = ple.amount_in_account_currency
|
||||
|
||||
# update voucher
|
||||
@@ -685,9 +695,10 @@ class ReceivablePayableReport(object):
|
||||
ple.party,
|
||||
ple.posting_date,
|
||||
ple.due_date,
|
||||
ple.account_currency.as_("currency"),
|
||||
ple.account_currency,
|
||||
ple.amount,
|
||||
ple.amount_in_account_currency,
|
||||
ple.remarks,
|
||||
)
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(self.qb_selection_filter))
|
||||
@@ -722,6 +733,7 @@ class ReceivablePayableReport(object):
|
||||
def prepare_conditions(self):
|
||||
self.qb_selection_filter = []
|
||||
party_type_field = scrub(self.party_type)
|
||||
self.qb_selection_filter.append(self.ple.party_type == self.party_type)
|
||||
|
||||
self.add_common_filters(party_type_field=party_type_field)
|
||||
|
||||
@@ -965,6 +977,9 @@ class ReceivablePayableReport(object):
|
||||
options="Supplier Group",
|
||||
)
|
||||
|
||||
if self.filters.show_remarks:
|
||||
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
|
||||
|
||||
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
|
||||
if not fieldname:
|
||||
fieldname = scrub(label)
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
|
||||
class TestAccountsReceivable(unittest.TestCase):
|
||||
def test_accounts_receivable(self):
|
||||
class TestAccountsReceivable(FrappeTestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_accounts_receivable(self):
|
||||
filters = {
|
||||
"company": "_Test Company 2",
|
||||
"based_on_payment_terms": 1,
|
||||
@@ -66,6 +74,50 @@ class TestAccountsReceivable(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_payment_againt_po_in_receivable_report(self):
|
||||
"""
|
||||
Payments made against Purchase Order will show up as outstanding amount
|
||||
"""
|
||||
|
||||
so = make_sales_order(
|
||||
company="_Test Company 2",
|
||||
customer="_Test Customer 2",
|
||||
warehouse="Finished Goods - _TC2",
|
||||
currency="EUR",
|
||||
debit_to="Debtors - _TC2",
|
||||
income_account="Sales - _TC2",
|
||||
expense_account="Cost of Goods Sold - _TC2",
|
||||
cost_center="Main - _TC2",
|
||||
)
|
||||
|
||||
pe = get_payment_entry(so.doctype, so.name)
|
||||
pe = pe.save().submit()
|
||||
|
||||
filters = {
|
||||
"company": "_Test Company 2",
|
||||
"based_on_payment_terms": 0,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
report = execute(filters)
|
||||
|
||||
expected_data_after_payment = [0, 1000, 0, -1000]
|
||||
|
||||
row = report[1][0]
|
||||
self.assertEqual(
|
||||
expected_data_after_payment,
|
||||
[
|
||||
row.invoiced,
|
||||
row.paid,
|
||||
row.credit_note,
|
||||
row.outstanding,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def make_sales_invoice():
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
@@ -535,7 +535,11 @@ def get_accounts(root_type, companies):
|
||||
):
|
||||
if account.account_name not in added_accounts:
|
||||
accounts.append(account)
|
||||
added_accounts.append(account.account_name)
|
||||
if account.account_number:
|
||||
account_key = account.account_number + "-" + account.account_name
|
||||
else:
|
||||
account_key = account.account_name
|
||||
added_accounts.append(account_key)
|
||||
|
||||
return accounts
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
frappe.query_reports["Gross Profit"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
@@ -12,46 +12,58 @@ frappe.query_reports["Gross Profit"] = {
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"sales_invoice",
|
||||
"fieldname": "sales_invoice",
|
||||
"label": __("Sales Invoice"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname":"group_by",
|
||||
"fieldname": "group_by",
|
||||
"label": __("Group By"),
|
||||
"fieldtype": "Select",
|
||||
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term",
|
||||
"default": "Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_group",
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_person",
|
||||
"label": __("Sales Person"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Person"
|
||||
},
|
||||
],
|
||||
"tree": true,
|
||||
"name_field": "parent",
|
||||
"parent_field": "parent_invoice",
|
||||
"initial_depth": 3,
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
|
||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
|
||||
column._options = "Sales Invoice";
|
||||
} else {
|
||||
column._options = "Item";
|
||||
}
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (data && (data.indent == 0.0 || row[1].content == "Total")) {
|
||||
if (data && (data.indent == 0.0 || (row[1] && row[1].content == "Total"))) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe import _, scrub
|
||||
from frappe.utils import cint, flt, formatdate
|
||||
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
|
||||
@@ -616,7 +617,7 @@ class GrossProfitGenerator(object):
|
||||
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
|
||||
|
||||
if previous_stock_value:
|
||||
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||
else:
|
||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||
else:
|
||||
@@ -676,6 +677,17 @@ class GrossProfitGenerator(object):
|
||||
if self.filters.to_date:
|
||||
conditions += " and posting_date <= %(to_date)s"
|
||||
|
||||
if self.filters.item_group:
|
||||
conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
|
||||
|
||||
if self.filters.sales_person:
|
||||
conditions += """
|
||||
and exists(select 1
|
||||
from `tabSales Team` st
|
||||
where st.parent = `tabSales Invoice`.name
|
||||
and st.sales_person = %(sales_person)s)
|
||||
"""
|
||||
|
||||
if self.filters.group_by == "Sales Person":
|
||||
sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives"
|
||||
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
|
||||
@@ -723,6 +735,7 @@ class GrossProfitGenerator(object):
|
||||
from
|
||||
`tabSales Invoice` inner join `tabSales Invoice Item`
|
||||
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
|
||||
join `tabItem` item on item.name = `tabSales Invoice Item`.item_code
|
||||
{sales_team_table}
|
||||
{payment_term_table}
|
||||
where
|
||||
|
||||
@@ -14,9 +14,9 @@ def execute(filters=None):
|
||||
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
columns = get_columns(filters)
|
||||
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
|
||||
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
|
||||
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
|
||||
final_result = group_by_supplier_and_category(res)
|
||||
|
||||
return columns, final_result
|
||||
|
||||
@@ -26,7 +26,6 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
supplier_map = get_supplier_pan_map()
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
print(journal_entry_party_map)
|
||||
|
||||
out = []
|
||||
for name, details in gle_map.items():
|
||||
|
||||
@@ -823,7 +823,13 @@ def get_held_invoices(party_type, party):
|
||||
|
||||
|
||||
def get_outstanding_invoices(
|
||||
party_type, party, account, common_filter=None, min_outstanding=None, max_outstanding=None
|
||||
party_type,
|
||||
party,
|
||||
account,
|
||||
common_filter=None,
|
||||
posting_date=None,
|
||||
min_outstanding=None,
|
||||
max_outstanding=None,
|
||||
):
|
||||
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
@@ -850,6 +856,7 @@ def get_outstanding_invoices(
|
||||
ple_query = QueryPaymentLedger()
|
||||
invoice_list = ple_query.get_voucher_outstandings(
|
||||
common_filter=common_filter,
|
||||
posting_date=posting_date,
|
||||
min_outstanding=min_outstanding,
|
||||
max_outstanding=max_outstanding,
|
||||
get_invoices=True,
|
||||
@@ -1417,6 +1424,7 @@ def create_payment_ledger_entry(
|
||||
"amount": dr_or_cr,
|
||||
"amount_in_account_currency": dr_or_cr_account_currency,
|
||||
"delinked": True if cancel else False,
|
||||
"remarks": gle.remarks,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1501,6 +1509,7 @@ class QueryPaymentLedger(object):
|
||||
# query filters
|
||||
self.vouchers = []
|
||||
self.common_filter = []
|
||||
self.voucher_posting_date = []
|
||||
self.min_outstanding = None
|
||||
self.max_outstanding = None
|
||||
|
||||
@@ -1571,6 +1580,7 @@ class QueryPaymentLedger(object):
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(filter_on_voucher_no))
|
||||
.where(Criterion.all(self.common_filter))
|
||||
.where(Criterion.all(self.voucher_posting_date))
|
||||
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
|
||||
)
|
||||
|
||||
@@ -1652,6 +1662,7 @@ class QueryPaymentLedger(object):
|
||||
self,
|
||||
vouchers=None,
|
||||
common_filter=None,
|
||||
posting_date=None,
|
||||
min_outstanding=None,
|
||||
max_outstanding=None,
|
||||
get_payments=False,
|
||||
@@ -1671,6 +1682,7 @@ class QueryPaymentLedger(object):
|
||||
self.reset()
|
||||
self.vouchers = vouchers
|
||||
self.common_filter = common_filter or []
|
||||
self.voucher_posting_date = posting_date or []
|
||||
self.min_outstanding = min_outstanding
|
||||
self.max_outstanding = max_outstanding
|
||||
self.get_payments = get_payments
|
||||
|
||||
@@ -1454,12 +1454,14 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
|
||||
return item
|
||||
|
||||
|
||||
def set_depreciation_settings_in_company():
|
||||
company = frappe.get_doc("Company", "_Test Company")
|
||||
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
|
||||
company.depreciation_expense_account = "_Test Depreciations - _TC"
|
||||
company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC"
|
||||
company.depreciation_cost_center = "_Test Cost Center - _TC"
|
||||
def set_depreciation_settings_in_company(company=None):
|
||||
if not company:
|
||||
company = "_Test Company"
|
||||
company = frappe.get_doc("Company", company)
|
||||
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - " + company.abbr
|
||||
company.depreciation_expense_account = "_Test Depreciations - " + company.abbr
|
||||
company.disposal_account = "_Test Gain/Loss on Asset Disposal - " + company.abbr
|
||||
company.depreciation_cost_center = "Main - " + company.abbr
|
||||
company.save()
|
||||
|
||||
# Enable booking asset depreciation entry automatically
|
||||
|
||||
@@ -76,7 +76,7 @@ frappe.ui.form.on('Asset Repair Consumed Item', {
|
||||
'warehouse': frm.doc.warehouse,
|
||||
'qty': item.consumed_quantity,
|
||||
'serial_no': item.serial_no,
|
||||
'company': frm.doc.company
|
||||
'company': frm.doc.company,
|
||||
};
|
||||
|
||||
frappe.call({
|
||||
|
||||
@@ -238,7 +238,6 @@
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
@@ -257,6 +256,7 @@
|
||||
"fieldname": "stock_entry",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Entry",
|
||||
"no_copy": 1,
|
||||
"options": "Stock Entry",
|
||||
"read_only": 1
|
||||
}
|
||||
@@ -264,10 +264,11 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-25 13:14:38.307723",
|
||||
"modified": "2022-08-16 15:55:25.023471",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -303,6 +304,7 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "asset_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
@@ -17,7 +17,7 @@ class AssetRepair(AccountsController):
|
||||
self.update_status()
|
||||
|
||||
if self.get("stock_items"):
|
||||
self.set_total_value()
|
||||
self.set_stock_items_cost()
|
||||
self.calculate_total_repair_cost()
|
||||
|
||||
def update_status(self):
|
||||
@@ -26,7 +26,7 @@ class AssetRepair(AccountsController):
|
||||
else:
|
||||
self.asset_doc.set_status()
|
||||
|
||||
def set_total_value(self):
|
||||
def set_stock_items_cost(self):
|
||||
for item in self.get("stock_items"):
|
||||
item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
|
||||
|
||||
@@ -66,6 +66,7 @@ class AssetRepair(AccountsController):
|
||||
if self.get("capitalize_repair_cost"):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||
self.make_gl_entries(cancel=True)
|
||||
self.db_set("stock_entry", None)
|
||||
if (
|
||||
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
|
||||
and self.increase_in_asset_life
|
||||
@@ -133,6 +134,7 @@ class AssetRepair(AccountsController):
|
||||
"qty": stock_item.consumed_quantity,
|
||||
"basic_rate": stock_item.valuation_rate,
|
||||
"serial_no": stock_item.serial_no,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -142,72 +144,42 @@ class AssetRepair(AccountsController):
|
||||
self.db_set("stock_entry", stock_entry.name)
|
||||
|
||||
def increase_stock_quantity(self):
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
stock_entry.flags.ignore_links = True
|
||||
stock_entry.cancel()
|
||||
if self.stock_entry:
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
stock_entry.flags.ignore_links = True
|
||||
stock_entry.cancel()
|
||||
|
||||
def make_gl_entries(self, cancel=False):
|
||||
if flt(self.repair_cost) > 0:
|
||||
if flt(self.total_repair_cost) > 0:
|
||||
gl_entries = self.get_gl_entries()
|
||||
make_gl_entries(gl_entries, cancel)
|
||||
|
||||
def get_gl_entries(self):
|
||||
gl_entries = []
|
||||
repair_and_maintenance_account = frappe.db.get_value(
|
||||
"Company", self.company, "repair_and_maintenance_account"
|
||||
)
|
||||
|
||||
fixed_asset_account = get_asset_account(
|
||||
"fixed_asset_account", asset=self.asset, company=self.company
|
||||
)
|
||||
expense_account = (
|
||||
self.get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account)
|
||||
self.get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account):
|
||||
if flt(self.repair_cost) <= 0:
|
||||
return
|
||||
|
||||
pi_expense_account = (
|
||||
frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"credit": self.repair_cost,
|
||||
"credit_in_account_currency": self.repair_cost,
|
||||
"against": repair_and_maintenance_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("stock_consumption"):
|
||||
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
for item in stock_entry.items:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"credit": item.amount,
|
||||
"credit_in_account_currency": item.amount,
|
||||
"against": repair_and_maintenance_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"debit": self.total_repair_cost,
|
||||
"debit_in_account_currency": self.total_repair_cost,
|
||||
"against": expense_account,
|
||||
"debit": self.repair_cost,
|
||||
"debit_in_account_currency": self.repair_cost,
|
||||
"against": pi_expense_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
@@ -220,7 +192,75 @@ class AssetRepair(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
return gl_entries
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": pi_expense_account,
|
||||
"credit": self.repair_cost,
|
||||
"credit_in_account_currency": self.repair_cost,
|
||||
"against": fixed_asset_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account):
|
||||
if not (self.get("stock_consumption") and self.get("stock_items")):
|
||||
return
|
||||
|
||||
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
|
||||
default_expense_account = None
|
||||
if not erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
default_expense_account = frappe.get_cached_value(
|
||||
"Company", self.company, "default_expense_account"
|
||||
)
|
||||
if not default_expense_account:
|
||||
frappe.throw(_("Please set default Expense Account in Company {0}").format(self.company))
|
||||
|
||||
for item in stock_entry.items:
|
||||
if flt(item.amount) > 0:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account or default_expense_account,
|
||||
"credit": item.amount,
|
||||
"credit_in_account_currency": item.amount,
|
||||
"against": fixed_asset_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"debit": item.amount,
|
||||
"debit_in_account_currency": item.amount,
|
||||
"against": item.expense_account or default_expense_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"against_voucher_type": "Stock Entry",
|
||||
"against_voucher": self.stock_entry,
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def modify_depreciation_schedule(self):
|
||||
for row in self.asset_doc.finance_books:
|
||||
|
||||
@@ -6,6 +6,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe.utils import flt, nowdate
|
||||
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account
|
||||
from erpnext.assets.doctype.asset.test_asset import (
|
||||
create_asset,
|
||||
create_asset_data,
|
||||
@@ -125,10 +126,109 @@ class TestAssetRepair(unittest.TestCase):
|
||||
asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
|
||||
self.assertTrue(asset_repair.purchase_invoice)
|
||||
|
||||
def test_gl_entries(self):
|
||||
asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
|
||||
gl_entry = frappe.get_last_doc("GL Entry")
|
||||
self.assertEqual(asset_repair.name, gl_entry.voucher_no)
|
||||
def test_gl_entries_with_perpetual_inventory(self):
|
||||
set_depreciation_settings_in_company(company="_Test Company with perpetual inventory")
|
||||
|
||||
asset_category = frappe.get_doc("Asset Category", "Computers")
|
||||
asset_category.append(
|
||||
"accounts",
|
||||
{
|
||||
"company_name": "_Test Company with perpetual inventory",
|
||||
"fixed_asset_account": "_Test Fixed Asset - TCP1",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
|
||||
"depreciation_expense_account": "_Test Depreciations - TCP1",
|
||||
},
|
||||
)
|
||||
asset_category.save()
|
||||
|
||||
asset_repair = create_asset_repair(
|
||||
capitalize_repair_cost=1,
|
||||
stock_consumption=1,
|
||||
warehouse="Stores - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
submit=1,
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account,
|
||||
sum(debit) as debit,
|
||||
sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
voucher_type='Asset Repair'
|
||||
and voucher_no=%s
|
||||
group by
|
||||
account
|
||||
""",
|
||||
asset_repair.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
fixed_asset_account = get_asset_account(
|
||||
"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
|
||||
)
|
||||
pi_expense_account = (
|
||||
frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account
|
||||
)
|
||||
stock_entry_expense_account = (
|
||||
frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account
|
||||
)
|
||||
|
||||
expected_values = {
|
||||
fixed_asset_account: [asset_repair.total_repair_cost, 0],
|
||||
pi_expense_account: [0, asset_repair.repair_cost],
|
||||
stock_entry_expense_account: [0, 100],
|
||||
}
|
||||
|
||||
for d in gl_entries:
|
||||
self.assertEqual(expected_values[d.account][0], d.debit)
|
||||
self.assertEqual(expected_values[d.account][1], d.credit)
|
||||
|
||||
def test_gl_entries_with_periodical_inventory(self):
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company", "default_expense_account", "Cost of Goods Sold - _TC"
|
||||
)
|
||||
asset_repair = create_asset_repair(
|
||||
capitalize_repair_cost=1,
|
||||
stock_consumption=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account,
|
||||
sum(debit) as debit,
|
||||
sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
voucher_type='Asset Repair'
|
||||
and voucher_no=%s
|
||||
group by
|
||||
account
|
||||
""",
|
||||
asset_repair.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
fixed_asset_account = get_asset_account(
|
||||
"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
|
||||
)
|
||||
default_expense_account = frappe.get_cached_value(
|
||||
"Company", asset_repair.company, "default_expense_account"
|
||||
)
|
||||
|
||||
expected_values = {fixed_asset_account: [1100, 0], default_expense_account: [0, 1100]}
|
||||
|
||||
for d in gl_entries:
|
||||
self.assertEqual(expected_values[d.account][0], d.debit)
|
||||
self.assertEqual(expected_values[d.account][1], d.credit)
|
||||
|
||||
def test_increase_in_asset_life(self):
|
||||
asset = create_asset(calculate_depreciation=1, submit=1)
|
||||
@@ -160,7 +260,7 @@ def create_asset_repair(**args):
|
||||
if args.asset:
|
||||
asset = args.asset
|
||||
else:
|
||||
asset = create_asset(is_existing_asset=1, submit=1)
|
||||
asset = create_asset(is_existing_asset=1, submit=1, company=args.company)
|
||||
asset_repair = frappe.new_doc("Asset Repair")
|
||||
asset_repair.update(
|
||||
{
|
||||
@@ -192,7 +292,7 @@ def create_asset_repair(**args):
|
||||
|
||||
if args.submit:
|
||||
asset_repair.repair_status = "Completed"
|
||||
asset_repair.cost_center = "_Test Cost Center - _TC"
|
||||
asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
|
||||
|
||||
if args.stock_consumption:
|
||||
stock_entry = frappe.get_doc(
|
||||
@@ -204,6 +304,8 @@ def create_asset_repair(**args):
|
||||
"t_warehouse": asset_repair.warehouse,
|
||||
"item_code": asset_repair.stock_items[0].item_code,
|
||||
"qty": asset_repair.stock_items[0].consumed_quantity,
|
||||
"basic_rate": args.rate if args.get("rate") is not None else 100,
|
||||
"cost_center": asset_repair.cost_center,
|
||||
},
|
||||
)
|
||||
stock_entry.submit()
|
||||
@@ -213,7 +315,13 @@ def create_asset_repair(**args):
|
||||
asset_repair.repair_cost = 1000
|
||||
if asset.calculate_depreciation:
|
||||
asset_repair.increase_in_asset_life = 12
|
||||
asset_repair.purchase_invoice = make_purchase_invoice().name
|
||||
pi = make_purchase_invoice(
|
||||
company=asset.company,
|
||||
expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"),
|
||||
cost_center=asset_repair.cost_center,
|
||||
warehouse=asset_repair.warehouse,
|
||||
)
|
||||
asset_repair.purchase_invoice = pi.name
|
||||
|
||||
asset_repair.submit()
|
||||
return asset_repair
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"label": "Subcontracting Settings"
|
||||
},
|
||||
{
|
||||
"default": "Material Transferred for Subcontract",
|
||||
"default": "BOM",
|
||||
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Backflush Raw Materials of Subcontract Based On",
|
||||
@@ -148,7 +148,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-31 19:40:26.103909",
|
||||
"modified": "2022-09-01 18:01:34.994657",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -15,9 +15,12 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts",
|
||||
filters: {'supplier': d.supplier}
|
||||
}
|
||||
query: "frappe.contacts.doctype.contact.contact.contact_query",
|
||||
filters: {
|
||||
link_doctype: "Supplier",
|
||||
link_name: d.supplier || ""
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -286,18 +286,6 @@ def get_list_context(context=None):
|
||||
return list_context
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select `tabContact`.name from `tabContact`, `tabDynamic Link`
|
||||
where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
|
||||
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
|
||||
limit %(page_len)s offset %(start)s""",
|
||||
{"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
|
||||
def postprocess(source, target_doc):
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
# Decompiled by https://python-decompiler.com
|
||||
|
||||
|
||||
import copy
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
@@ -11,10 +13,12 @@ from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_
|
||||
execute,
|
||||
)
|
||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||
get_rm_items,
|
||||
get_subcontracting_order,
|
||||
make_service_item,
|
||||
make_stock_in_entry,
|
||||
make_stock_transfer_entry,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
make_subcontracting_receipt,
|
||||
)
|
||||
@@ -36,15 +40,18 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase):
|
||||
sco = get_subcontracting_order(
|
||||
service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code="_Test Item Home Desktop 100",
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
qty=100,
|
||||
basic_rate=100,
|
||||
rm_items = get_rm_items(sco.supplied_items)
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
|
||||
for item in rm_items:
|
||||
item["sco_rm_detail"] = sco.items[0].name
|
||||
|
||||
make_stock_transfer_entry(
|
||||
sco_no=sco.name,
|
||||
rm_items=rm_items,
|
||||
itemwise_details=copy.deepcopy(itemwise_details),
|
||||
)
|
||||
|
||||
make_subcontracting_receipt_against_sco(sco.name)
|
||||
sco.reload()
|
||||
col, data = execute(
|
||||
|
||||
105
erpnext/change_log/v14/v14_0_0.md
Normal file
105
erpnext/change_log/v14/v14_0_0.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Version 14.0.0 Release Notes
|
||||
|
||||
### Accounting
|
||||
|
||||
- [Improved Indian Compliance and GST APIs](https://docs.erpnext.com/docs/v14/user/manual/en/regional/india)
|
||||
- [Common Party Accounting](https://docs.erpnext.com/docs/v14/user/manual/en/accounts/articles/common_party_accounting)
|
||||
- [Provisional accounting for expenses](https://github.com/frappe/erpnext/pull/29451)
|
||||
- [Discount Accounting](https://github.com/frappe/erpnext/pull/26359)
|
||||
- [New Payment Reconciliation Tool](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/payment-reconciliation)
|
||||
- [Coupon Code in POS](https://github.com/frappe/erpnext/pull/27004)
|
||||
- [Configurable cost center allocation](https://docs.erpnext.com/docs/v14/user/manual/en/cost_center_allocation)
|
||||
- [Payment Ledger](https://docs.erpnext.com/docs/v14/user/manual/en/accounts/articles/payment_ledger)
|
||||
- [Cash and Non trade discounts in Sales Invoice](https://github.com/frappe/erpnext/pull/31405)
|
||||
- [Improved TaxJar Integration](https://docs.erpnext.com/docs/v14/user/manual/en/erpnext_integration/taxjar_integration)
|
||||
- [KSA E-Invoicing](https://docs.erpnext.com/docs/v14/user/manual/en/simplified_ksa_vat_management_and_reporting)
|
||||
- [South Africa VAT Audit Report](https://docs.erpnext.com/docs/v14/user/manual/en/regional/south_africa/vat_audit_report)
|
||||
- [E Invoice Eway Bill Distance is calculated automatically](https://github.com/frappe/erpnext/pull/30908)
|
||||
- [Payment Terms Status report](https://github.com/frappe/erpnext/pull/29137)
|
||||
- [Merge POS invoices based on customer group](https://github.com/frappe/erpnext/pull/27471)
|
||||
- [Ledger Merger](https://github.com/frappe/erpnext/pull/28812)
|
||||
- [Increase number of supported currency exchanges](https://github.com/frappe/erpnext/pull/26763)
|
||||
|
||||
|
||||
|
||||
### Stock
|
||||
- [LIFO Valuation](https://github.com/frappe/erpnext/pull/29296)
|
||||
- [Batch-wise Valuation Rates](https://github.com/frappe/erpnext/pull/29804)
|
||||
- [Better Barcode Scanning](https://github.com/frappe/erpnext/pull/30516)
|
||||
- [Over transfer allowance for material transfers](https://github.com/frappe/erpnext/pull/26264)
|
||||
- [Scanning in Pick List](https://github.com/frappe/erpnext/pull/30832)
|
||||
- [GLE reposting with progress and chunking for backdated entries](https://github.com/frappe/erpnext/pull/31343)
|
||||
|
||||
### E-Commerce
|
||||
- [Redesigned E-commerce Portal](https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce)
|
||||
- [E-commerce Search](https://docs.erpnext.com/docs/v14/user/manual/en/e_commerce/e_commerce_search)
|
||||
|
||||
|
||||
### Assets
|
||||
- [Asset Splitting](https://github.com/frappe/erpnext/pull/29350)
|
||||
- [Grouped Asset](https://github.com/frappe/erpnext/pull/29334)
|
||||
- [Asset Repair](https://github.com/frappe/erpnext/pull/25798)
|
||||
- [Consume serialized items during Asset Repair](https://github.com/frappe/erpnext/pull/28349)
|
||||
|
||||
### Manufacturing
|
||||
- [Faster BOM Update Tool](https://github.com/frappe/erpnext/pull/31078)
|
||||
- [Scrap Item in Job Card](https://github.com/frappe/erpnext/pull/27518)
|
||||
- [Process Loss in manufacturing](https://github.com/frappe/erpnext/pull/26151)
|
||||
- [Production Plan Summary Report](https://github.com/frappe/erpnext/pull/26240)
|
||||
- [Work Order Consumed Materials Report](https://github.com/frappe/erpnext/pull/28500)
|
||||
- [Provision to close the Work Order](https://github.com/frappe/erpnext/pull/28150)
|
||||
- [Provision to aggregate subassembly items in production plan](https://github.com/frappe/erpnext/pull/28939)
|
||||
|
||||
### Subcontracting
|
||||
- [New Subcontracting Module](https://github.com/frappe/erpnext/pull/30955)
|
||||
- [Subcontracted Purchase Order from the Production Plan](https://github.com/frappe/erpnext/pull/26240)
|
||||
|
||||
|
||||
### CRM
|
||||
- [Refreshed CRM Flows](https://github.com/frappe/erpnext/pull/31311)
|
||||
- [New Prospect document](https://github.com/frappe/erpnext/pull/27102)
|
||||
- [CRM Settings Page](https://docs.erpnext.com/docs/v13/user/manual/en/CRM/crm_settings)
|
||||
- [Competitor Tagging in Opportunity and Quotation](https://github.com/frappe/erpnext/pull/28050)
|
||||
- [Sales Pipeline Analytics Report](https://github.com/frappe/erpnext/pull/26639)
|
||||
- [Opportunity Summary by Sales Stage Report](https://github.com/frappe/erpnext/pull/26639)
|
||||
|
||||
|
||||
### HR & Payroll
|
||||
- [Organizational Chart](https://github.com/frappe/erpnext/pull/26261)
|
||||
- [Full and Final Settlement](https://github.com/frappe/erpnext/pull/26364)
|
||||
- [Income tax computation Report](https://github.com/frappe/erpnext/pull/29963)
|
||||
- [Employee Grievance](https://github.com/frappe/erpnext/pull/25705)
|
||||
- [Tax for recurring additional salary](https://github.com/frappe/erpnext/pull/27459)
|
||||
- [Tracking Multi-round interview](https://github.com/frappe/erpnext/pull/25482)
|
||||
- [Exit Interview and Employee Exits Report](https://github.com/frappe/erpnext/pull/28741)
|
||||
- [Leave Type configuration to allow over allocation](https://github.com/frappe/erpnext/pull/30940)
|
||||
- [Employee Reminders](https://github.com/frappe/erpnext/pull/25735)
|
||||
- [Refactored Employee Leave Balance](https://github.com/frappe/erpnext/pull/29439)
|
||||
|
||||
### Healthcare
|
||||
- [Treatment Plan Template](https://github.com/frappe/erpnext/pull/26557)
|
||||
- [Capacity for Service Unit, concurrent appointments based on capacity, Patient Appointments](https://github.com/frappe/erpnext/pull/27219)
|
||||
- [UOM specific barcode](https://docs.erpnext.com/docs/v14/user/manual/en/stock/articles/track-items-using-barcode#uom-specific-barcode)
|
||||
- [Redesigned Patient History and Patient Progress](https://github.com/frappe/erpnext/pull/27100)
|
||||
|
||||
|
||||
### New apps
|
||||
The following modules has been separated out from ERPNext and new apps has been created.
|
||||
|
||||
- [HR and Payroll](https://github.com/frappe/hrms)
|
||||
- [Healthcare](https://github.com/frappe/health)
|
||||
- [Education](https://github.com/frappe/education)
|
||||
- [E-commerce Integration](https://github.com/frappe/ecommerce_integrations)
|
||||
- [Hospitality](https://github.com/frappe/hospitality)
|
||||
- [Non-Profit](https://github.com/frappe/non_profit)
|
||||
- [Agriculture](https://github.com/frappe/agriculture)
|
||||
- [Datev Integration](https://github.com/alyf-de/erpnext_datev)
|
||||
- [Germany Localisation](https://github.com/alyf-de/erpnext_germany)
|
||||
|
||||
### Others
|
||||
- [Unicommerce Integration](https://docs.erpnext.com/docs/v13/user/manual/en/erpnext_integration/unicommerce_integration)
|
||||
- [Bulk Transaction Processing](https://github.com/frappe/erpnext/pull/28580)
|
||||
- [Refactored Document Naming Settings](https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/settings/document-naming-settings)
|
||||
- [Project Portal Enhancements](https://github.com/frappe/erpnext/pull/26090)
|
||||
- [Refund entry against loans](https://github.com/frappe/erpnext/pull/29460)
|
||||
- [Bank Reconciliation for loan documents](https://github.com/frappe/erpnext/pull/29865)
|
||||
@@ -1109,17 +1109,17 @@ class AccountsController(TransactionBase):
|
||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
dr_or_cr = "credit"
|
||||
rev_dr_cr = "debit"
|
||||
supplier_or_customer = self.supplier
|
||||
|
||||
else:
|
||||
dr_or_cr = "debit"
|
||||
rev_dr_cr = "credit"
|
||||
supplier_or_customer = self.customer
|
||||
|
||||
if enable_discount_accounting:
|
||||
if self.doctype == "Purchase Invoice":
|
||||
dr_or_cr = "credit"
|
||||
rev_dr_cr = "debit"
|
||||
supplier_or_customer = self.supplier
|
||||
|
||||
else:
|
||||
dr_or_cr = "debit"
|
||||
rev_dr_cr = "credit"
|
||||
supplier_or_customer = self.customer
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get("discount_amount") and item.get("discount_account"):
|
||||
discount_amount = item.discount_amount * item.qty
|
||||
@@ -1173,18 +1173,22 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("discount_amount") and self.get("additional_discount_account"):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: self.discount_amount,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
if (
|
||||
(enable_discount_accounting or self.get("is_cash_or_non_trade_discount"))
|
||||
and self.get("additional_discount_account")
|
||||
and self.get("discount_amount")
|
||||
):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: self.discount_amount,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
|
||||
from erpnext.controllers.status_updater import get_allowance_for
|
||||
|
||||
@@ -86,6 +86,7 @@ class BuyingController(SubcontractingController):
|
||||
company=self.company,
|
||||
party_address=self.get("supplier_address"),
|
||||
shipping_address=self.get("shipping_address"),
|
||||
company_address=self.get("billing_address"),
|
||||
fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
|
||||
ignore_permissions=self.flags.ignore_permissions,
|
||||
)
|
||||
|
||||
@@ -18,8 +18,9 @@ from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Employee"
|
||||
conditions = []
|
||||
fields = get_fields("Employee", ["name", "employee_name"])
|
||||
fields = get_fields(doctype, ["name", "employee_name"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabEmployee`
|
||||
@@ -49,7 +50,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
|
||||
doctype = "Lead"
|
||||
fields = get_fields(doctype, ["name", "lead_name", "company_name"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabLead`
|
||||
@@ -77,6 +79,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Customer"
|
||||
conditions = []
|
||||
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
|
||||
|
||||
@@ -85,9 +88,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
else:
|
||||
fields = ["name", "customer_name", "customer_group", "territory"]
|
||||
|
||||
fields = get_fields("Customer", fields)
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
searchfields = frappe.get_meta("Customer").get_search_fields()
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
|
||||
|
||||
return frappe.db.sql(
|
||||
@@ -116,6 +119,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Supplier"
|
||||
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
|
||||
|
||||
if supp_master_name == "Supplier Name":
|
||||
@@ -123,7 +127,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
else:
|
||||
fields = ["name", "supplier_name", "supplier_group"]
|
||||
|
||||
fields = get_fields("Supplier", fields)
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {field} from `tabSupplier`
|
||||
@@ -147,6 +151,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Account"
|
||||
company_currency = erpnext.get_company_currency(filters.get("company"))
|
||||
|
||||
def get_accounts(with_account_type_filter):
|
||||
@@ -197,13 +202,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
doctype = "Item"
|
||||
conditions = []
|
||||
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
# Get searchfields from meta and use in Item Link field query
|
||||
meta = frappe.get_meta("Item", cached=True)
|
||||
meta = frappe.get_meta(doctype, cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
# these are handled separately
|
||||
@@ -257,7 +263,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
filters.pop("supplier", None)
|
||||
|
||||
description_cond = ""
|
||||
if frappe.db.count("Item", cache=True) < 50000:
|
||||
if frappe.db.count(doctype, cache=True) < 50000:
|
||||
# scan description only if items are less than 50000
|
||||
description_cond = "or tabItem.description LIKE %(txt)s"
|
||||
return frappe.db.sql(
|
||||
@@ -300,8 +306,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "BOM"
|
||||
conditions = []
|
||||
fields = get_fields("BOM", ["name", "item"])
|
||||
fields = get_fields(doctype, ["name", "item"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields}
|
||||
@@ -331,6 +338,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Project"
|
||||
cond = ""
|
||||
if filters and filters.get("customer"):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
@@ -338,8 +346,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
frappe.db.escape(filters.get("customer"))
|
||||
)
|
||||
|
||||
fields = get_fields("Project", ["name", "project_name"])
|
||||
searchfields = frappe.get_meta("Project").get_search_fields()
|
||||
fields = get_fields(doctype, ["name", "project_name"])
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql(
|
||||
@@ -366,7 +374,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
|
||||
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
|
||||
doctype = "Delivery Note"
|
||||
fields = get_fields(doctype, ["name", "customer", "posting_date"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
@@ -402,6 +411,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Batch"
|
||||
cond = ""
|
||||
if filters.get("posting_date"):
|
||||
cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)"
|
||||
@@ -420,7 +430,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("is_return"):
|
||||
having_clause = ""
|
||||
|
||||
meta = frappe.get_meta("Batch", cached=True)
|
||||
meta = frappe.get_meta(doctype, cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
search_columns = ""
|
||||
@@ -496,6 +506,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Account"
|
||||
filter_list = []
|
||||
|
||||
if isinstance(filters, dict):
|
||||
@@ -514,7 +525,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
|
||||
|
||||
return frappe.desk.reportview.execute(
|
||||
"Account",
|
||||
doctype,
|
||||
filters=filter_list,
|
||||
fields=["name", "parent_account"],
|
||||
limit_start=start,
|
||||
@@ -553,6 +564,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
@@ -628,6 +640,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
@@ -650,6 +663,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
# Should be used when item code is passed in filters.
|
||||
doctype = "Warehouse"
|
||||
conditions, bin_conditions = [], []
|
||||
filter_dict = get_doctype_wise_filters(filters)
|
||||
|
||||
|
||||
@@ -311,6 +311,7 @@ class SellingController(StockController):
|
||||
"sales_invoice_item": d.get("sales_invoice_item"),
|
||||
"dn_detail": d.get("dn_detail"),
|
||||
"incoming_rate": p.get("incoming_rate"),
|
||||
"item_row": p,
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -334,6 +335,7 @@ class SellingController(StockController):
|
||||
"sales_invoice_item": d.get("sales_invoice_item"),
|
||||
"dn_detail": d.get("dn_detail"),
|
||||
"incoming_rate": d.get("incoming_rate"),
|
||||
"item_row": d,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -36,6 +36,10 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class BatchExpiredError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class StockController(AccountsController):
|
||||
def validate(self):
|
||||
super(StockController, self).validate()
|
||||
@@ -77,6 +81,10 @@ class StockController(AccountsController):
|
||||
def validate_serialized_batch(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
is_material_issue = False
|
||||
if self.doctype == "Stock Entry" and self.purpose == "Material Issue":
|
||||
is_material_issue = True
|
||||
|
||||
for d in self.get("items"):
|
||||
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
|
||||
serial_nos = frappe.get_all(
|
||||
@@ -93,6 +101,9 @@ class StockController(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
if is_material_issue:
|
||||
continue
|
||||
|
||||
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
|
||||
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
||||
|
||||
@@ -100,7 +111,8 @@ class StockController(AccountsController):
|
||||
frappe.throw(
|
||||
_("Row #{0}: The batch {1} has already expired.").format(
|
||||
d.idx, get_link_to_form("Batch", d.get("batch_no"))
|
||||
)
|
||||
),
|
||||
BatchExpiredError,
|
||||
)
|
||||
|
||||
def clean_serial_nos(self):
|
||||
@@ -310,7 +322,13 @@ class StockController(AccountsController):
|
||||
)
|
||||
if (
|
||||
self.doctype
|
||||
not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry")
|
||||
not in (
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Stock Reconciliation",
|
||||
"Stock Entry",
|
||||
"Subcontracting Receipt",
|
||||
)
|
||||
and not is_expense_account
|
||||
):
|
||||
frappe.throw(
|
||||
@@ -372,11 +390,38 @@ class StockController(AccountsController):
|
||||
return sl_dict
|
||||
|
||||
def update_inventory_dimensions(self, row, sl_dict) -> None:
|
||||
# To handle delivery note and sales invoice
|
||||
if row.get("item_row"):
|
||||
row = row.get("item_row")
|
||||
|
||||
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
|
||||
for dimension in dimensions:
|
||||
if dimension and row.get(dimension.source_fieldname):
|
||||
if not dimension:
|
||||
continue
|
||||
|
||||
if row.get(dimension.source_fieldname):
|
||||
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||
|
||||
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
|
||||
sl_dict[dimension.target_fieldname] = self.get(dimension.fetch_from_parent)
|
||||
|
||||
# Get value based on doctype name
|
||||
if not sl_dict.get(dimension.target_fieldname):
|
||||
fieldname = frappe.get_cached_value(
|
||||
"DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
|
||||
)
|
||||
|
||||
if not fieldname:
|
||||
fieldname = frappe.get_cached_value(
|
||||
"Custom Field", {"dt": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
|
||||
)
|
||||
|
||||
if fieldname and self.get(fieldname):
|
||||
sl_dict[dimension.target_fieldname] = self.get(fieldname)
|
||||
|
||||
if sl_dict[dimension.target_fieldname] and self.docstatus == 1:
|
||||
row.db_set(dimension.source_fieldname, sl_dict[dimension.target_fieldname])
|
||||
|
||||
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||
from erpnext.stock.stock_ledger import make_sl_entries
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@ class SubcontractingController(StockController):
|
||||
row.item_code,
|
||||
row.get(self.subcontract_data.order_field),
|
||||
) and transfer_item.qty > 0:
|
||||
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
|
||||
qty = flt(self.__get_qty_based_on_material_transfer(row, transfer_item))
|
||||
transfer_item.qty -= qty
|
||||
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
|
||||
|
||||
@@ -720,6 +720,25 @@ class SubcontractingController(StockController):
|
||||
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
||||
sco_doc.update_status()
|
||||
|
||||
def set_missing_values_in_additional_costs(self):
|
||||
self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
|
||||
|
||||
if self.total_additional_costs:
|
||||
if self.distribute_additional_costs_based_on == "Amount":
|
||||
total_amt = sum(flt(item.amount) for item in self.get("items"))
|
||||
for item in self.items:
|
||||
item.additional_cost_per_qty = (
|
||||
(item.amount * self.total_additional_costs) / total_amt
|
||||
) / item.qty
|
||||
else:
|
||||
total_qty = sum(flt(item.qty) for item in self.get("items"))
|
||||
additional_cost_per_qty = self.total_additional_costs / total_qty
|
||||
for item in self.items:
|
||||
item.additional_cost_per_qty = additional_cost_per_qty
|
||||
else:
|
||||
for item in self.items:
|
||||
item.additional_cost_per_qty = 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_current_stock(self):
|
||||
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
||||
@@ -730,7 +749,7 @@ class SubcontractingController(StockController):
|
||||
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
|
||||
"actual_qty",
|
||||
)
|
||||
item.current_stock = flt(actual_qty) or 0
|
||||
item.current_stock = flt(actual_qty)
|
||||
|
||||
@property
|
||||
def sub_contracted_items(self):
|
||||
|
||||
@@ -37,6 +37,12 @@ class calculate_taxes_and_totals(object):
|
||||
self.set_discount_amount()
|
||||
self.apply_discount_amount()
|
||||
|
||||
# Update grand total as per cash and non trade discount
|
||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||
self.doc.grand_total -= self.doc.discount_amount
|
||||
self.doc.base_grand_total -= self.doc.base_discount_amount
|
||||
self.set_rounded_total()
|
||||
|
||||
self.calculate_shipping_charges()
|
||||
|
||||
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
@@ -500,9 +506,6 @@ class calculate_taxes_and_totals(object):
|
||||
else:
|
||||
self.doc.grand_total = flt(self.doc.net_total)
|
||||
|
||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||
self.doc.grand_total -= self.doc.discount_amount
|
||||
|
||||
if self.doc.get("taxes"):
|
||||
self.doc.total_taxes_and_charges = flt(
|
||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
||||
@@ -597,16 +600,16 @@ class calculate_taxes_and_totals(object):
|
||||
if not self.doc.apply_discount_on:
|
||||
frappe.throw(_("Please select Apply Discount On"))
|
||||
|
||||
self.doc.base_discount_amount = flt(
|
||||
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
||||
)
|
||||
|
||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
|
||||
"is_cash_or_non_trade_discount"
|
||||
):
|
||||
self.discount_amount_applied = True
|
||||
return
|
||||
|
||||
self.doc.base_discount_amount = flt(
|
||||
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
||||
)
|
||||
|
||||
total_for_discount_amount = self.get_total_for_discount_amount()
|
||||
taxes = self.doc.get("taxes")
|
||||
net_total = 0
|
||||
@@ -767,6 +770,18 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.precision("outstanding_amount"),
|
||||
)
|
||||
|
||||
if (
|
||||
self.doc.doctype == "Sales Invoice"
|
||||
and self.doc.get("is_pos")
|
||||
and self.doc.get("pos_profile")
|
||||
and self.doc.get("is_consolidated")
|
||||
):
|
||||
write_off_limit = flt(
|
||||
frappe.db.get_value("POS Profile", self.doc.pos_profile, "write_off_limit")
|
||||
)
|
||||
if write_off_limit and abs(self.doc.outstanding_amount) <= write_off_limit:
|
||||
self.doc.write_off_outstanding_amount_automatically = 1
|
||||
|
||||
if (
|
||||
self.doc.doctype == "Sales Invoice"
|
||||
and self.doc.get("is_pos")
|
||||
|
||||
@@ -36,6 +36,36 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
sco.remove_empty_rows()
|
||||
self.assertEqual((len_before - 1), len(sco.service_items))
|
||||
|
||||
def test_set_missing_values_in_additional_costs(self):
|
||||
sco = get_subcontracting_order(do_not_submit=1)
|
||||
|
||||
rate_without_additional_cost = sco.items[0].rate
|
||||
amount_without_additional_cost = sco.items[0].amount
|
||||
|
||||
additional_amount = 120
|
||||
sco.append(
|
||||
"additional_costs",
|
||||
{
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"description": "Test",
|
||||
"amount": additional_amount,
|
||||
},
|
||||
)
|
||||
sco.save()
|
||||
|
||||
additional_cost_per_qty = additional_amount / sco.items[0].qty
|
||||
|
||||
self.assertEqual(sco.items[0].additional_cost_per_qty, additional_cost_per_qty)
|
||||
self.assertEqual(rate_without_additional_cost + additional_cost_per_qty, sco.items[0].rate)
|
||||
self.assertEqual(amount_without_additional_cost + additional_amount, sco.items[0].amount)
|
||||
|
||||
sco.additional_costs = []
|
||||
sco.save()
|
||||
|
||||
self.assertEqual(sco.items[0].additional_cost_per_qty, 0)
|
||||
self.assertEqual(rate_without_additional_cost, sco.items[0].rate)
|
||||
self.assertEqual(amount_without_additional_cost, sco.items[0].amount)
|
||||
|
||||
def test_create_raw_materials_supplied(self):
|
||||
sco = get_subcontracting_order()
|
||||
sco.supplied_items = None
|
||||
|
||||
@@ -7,7 +7,7 @@ from collections import Counter
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_url, getdate
|
||||
from frappe.utils import get_url, getdate, now
|
||||
from frappe.utils.verified_command import get_signed_params
|
||||
|
||||
|
||||
@@ -104,16 +104,28 @@ class Appointment(Document):
|
||||
# Return if already linked
|
||||
if self.party:
|
||||
return
|
||||
|
||||
lead = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Lead",
|
||||
"lead_name": self.customer_name,
|
||||
"email_id": self.customer_email,
|
||||
"notes": self.customer_details,
|
||||
"phone": self.customer_phone_number,
|
||||
}
|
||||
)
|
||||
|
||||
if self.customer_details:
|
||||
lead.append(
|
||||
"notes",
|
||||
{
|
||||
"note": self.customer_details,
|
||||
"added_by": frappe.session.user,
|
||||
"added_on": now(),
|
||||
},
|
||||
)
|
||||
|
||||
lead.insert(ignore_permissions=True)
|
||||
|
||||
# Link lead
|
||||
self.party = lead.name
|
||||
|
||||
|
||||
@@ -6,29 +6,20 @@ import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def create_test_lead():
|
||||
test_lead = frappe.db.get_value("Lead", {"email_id": "test@example.com"})
|
||||
if test_lead:
|
||||
return frappe.get_doc("Lead", test_lead)
|
||||
test_lead = frappe.get_doc(
|
||||
{"doctype": "Lead", "lead_name": "Test Lead", "email_id": "test@example.com"}
|
||||
)
|
||||
test_lead.insert(ignore_permissions=True)
|
||||
return test_lead
|
||||
LEAD_EMAIL = "test_appointment_lead@example.com"
|
||||
|
||||
|
||||
def create_test_appointments():
|
||||
def create_test_appointment():
|
||||
test_appointment = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Appointment",
|
||||
"email": "test@example.com",
|
||||
"status": "Open",
|
||||
"customer_name": "Test Lead",
|
||||
"customer_phone_number": "666",
|
||||
"customer_skype": "test",
|
||||
"customer_email": "test@example.com",
|
||||
"customer_email": LEAD_EMAIL,
|
||||
"scheduled_time": datetime.datetime.now(),
|
||||
"customer_details": "Hello, Friend!",
|
||||
}
|
||||
)
|
||||
test_appointment.insert()
|
||||
@@ -36,16 +27,16 @@ def create_test_appointments():
|
||||
|
||||
|
||||
class TestAppointment(unittest.TestCase):
|
||||
test_appointment = test_lead = None
|
||||
def setUpClass():
|
||||
frappe.db.delete("Lead", {"email_id": LEAD_EMAIL})
|
||||
|
||||
def setUp(self):
|
||||
self.test_lead = create_test_lead()
|
||||
self.test_appointment = create_test_appointments()
|
||||
self.test_appointment = create_test_appointment()
|
||||
self.test_appointment.set_verified(self.test_appointment.customer_email)
|
||||
|
||||
def test_calendar_event_created(self):
|
||||
cal_event = frappe.get_doc("Event", self.test_appointment.calendar_event)
|
||||
self.assertEqual(cal_event.starts_on, self.test_appointment.scheduled_time)
|
||||
|
||||
def test_lead_linked(self):
|
||||
lead = frappe.get_doc("Lead", self.test_lead.name)
|
||||
self.assertIsNotNone(lead)
|
||||
self.assertTrue(self.test_appointment.party)
|
||||
|
||||
@@ -340,8 +340,8 @@
|
||||
"fieldname": "no_of_employees",
|
||||
"fieldtype": "Select",
|
||||
"label": "No of Employees",
|
||||
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
|
||||
},
|
||||
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_22",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -514,7 +514,7 @@
|
||||
"idx": 5,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2022-07-22 15:55:03.176094",
|
||||
"modified": "2022-08-09 18:26:17.101521",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead",
|
||||
|
||||
@@ -463,7 +463,7 @@
|
||||
"fieldname": "no_of_employees",
|
||||
"fieldtype": "Select",
|
||||
"label": "No of Employees",
|
||||
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
|
||||
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
|
||||
},
|
||||
{
|
||||
"fieldname": "annual_revenue",
|
||||
@@ -622,7 +622,7 @@
|
||||
"icon": "fa fa-info-sign",
|
||||
"idx": 195,
|
||||
"links": [],
|
||||
"modified": "2022-07-22 18:46:32.858696",
|
||||
"modified": "2022-08-09 18:26:37.235964",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity",
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"fieldname": "no_of_employees",
|
||||
"fieldtype": "Select",
|
||||
"label": "No. of Employees",
|
||||
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
|
||||
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
|
||||
},
|
||||
{
|
||||
"fieldname": "annual_revenue",
|
||||
@@ -218,7 +218,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-22 15:10:26.887502",
|
||||
"modified": "2022-08-09 18:26:56.950185",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Prospect",
|
||||
|
||||
@@ -12,7 +12,9 @@ from decimal import Decimal
|
||||
import frappe
|
||||
from bs4 import BeautifulSoup as bs
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.custom.doctype.custom_field.custom_field import (
|
||||
create_custom_fields as _create_custom_fields,
|
||||
)
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.data import format_datetime
|
||||
|
||||
@@ -577,22 +579,25 @@ class TallyMigration(Document):
|
||||
new_year.save()
|
||||
oldest_year = new_year
|
||||
|
||||
def create_custom_fields(doctypes):
|
||||
tally_guid_df = {
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "tally_guid",
|
||||
"read_only": 1,
|
||||
"label": "Tally GUID",
|
||||
}
|
||||
tally_voucher_no_df = {
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "tally_voucher_no",
|
||||
"read_only": 1,
|
||||
"label": "Tally Voucher Number",
|
||||
}
|
||||
for df in [tally_guid_df, tally_voucher_no_df]:
|
||||
for doctype in doctypes:
|
||||
create_custom_field(doctype, df)
|
||||
def create_custom_fields():
|
||||
_create_custom_fields(
|
||||
{
|
||||
("Journal Entry", "Purchase Invoice", "Sales Invoice"): [
|
||||
{
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "tally_guid",
|
||||
"read_only": 1,
|
||||
"label": "Tally GUID",
|
||||
},
|
||||
{
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "tally_voucher_no",
|
||||
"read_only": 1,
|
||||
"label": "Tally Voucher Number",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
def create_price_list():
|
||||
frappe.get_doc(
|
||||
@@ -628,7 +633,7 @@ class TallyMigration(Document):
|
||||
|
||||
create_fiscal_years(vouchers)
|
||||
create_price_list()
|
||||
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
|
||||
create_custom_fields()
|
||||
|
||||
total = len(vouchers)
|
||||
is_last = False
|
||||
|
||||
@@ -6,7 +6,7 @@ from urllib.parse import urlparse
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
|
||||
@@ -19,27 +19,24 @@ class WoocommerceSettings(Document):
|
||||
|
||||
def create_delete_custom_fields(self):
|
||||
if self.enable_sync:
|
||||
custom_fields = {}
|
||||
# create
|
||||
for doctype in ["Customer", "Sales Order", "Item", "Address"]:
|
||||
df = dict(
|
||||
fieldname="woocommerce_id",
|
||||
label="Woocommerce ID",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
print_hide=1,
|
||||
)
|
||||
create_custom_field(doctype, df)
|
||||
|
||||
for doctype in ["Customer", "Address"]:
|
||||
df = dict(
|
||||
fieldname="woocommerce_email",
|
||||
label="Woocommerce Email",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
print_hide=1,
|
||||
)
|
||||
create_custom_field(doctype, df)
|
||||
create_custom_fields(
|
||||
{
|
||||
("Customer", "Sales Order", "Item", "Address"): dict(
|
||||
fieldname="woocommerce_id",
|
||||
label="Woocommerce ID",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
print_hide=1,
|
||||
),
|
||||
("Customer", "Address"): dict(
|
||||
fieldname="woocommerce_email",
|
||||
label="Woocommerce Email",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
print_hide=1,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}):
|
||||
item_group = frappe.new_doc("Item Group")
|
||||
|
||||
@@ -26,6 +26,7 @@ def handle_incoming_call(**kwargs):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
exotel_settings.log_error("Error in Exotel incoming call")
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
|
||||
@@ -507,6 +507,7 @@ accounting_dimension_doctypes = [
|
||||
"Shipping Rule",
|
||||
"Landed Cost Item",
|
||||
"Asset Value Adjustment",
|
||||
"Asset Repair",
|
||||
"Loyalty Program",
|
||||
"Stock Reconciliation",
|
||||
"POS Profile",
|
||||
@@ -519,6 +520,10 @@ accounting_dimension_doctypes = [
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Sales Order",
|
||||
"Subcontracting Order",
|
||||
"Subcontracting Order Item",
|
||||
"Subcontracting Receipt",
|
||||
"Subcontracting Receipt Item",
|
||||
]
|
||||
|
||||
# get matching queries for Bank Reconciliation
|
||||
|
||||
@@ -135,7 +135,11 @@ def calculate_accrual_amount_for_demand_loans(
|
||||
def make_accrual_interest_entry_for_demand_loans(
|
||||
posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"
|
||||
):
|
||||
query_filters = {"status": ("in", ["Disbursed", "Partially Disbursed"]), "docstatus": 1}
|
||||
query_filters = {
|
||||
"status": ("in", ["Disbursed", "Partially Disbursed"]),
|
||||
"docstatus": 1,
|
||||
"is_term_loan": 0,
|
||||
}
|
||||
|
||||
if loan_type:
|
||||
query_filters.update({"loan_type": loan_type})
|
||||
@@ -229,6 +233,7 @@ def get_term_loans(date, term_loan=None, loan_type=None):
|
||||
AND l.is_term_loan =1
|
||||
AND rs.payment_date <= %s
|
||||
AND rs.is_accrued=0 {0}
|
||||
AND rs.principal_amount > 0
|
||||
AND l.status = 'Disbursed'
|
||||
ORDER BY rs.payment_date""".format(
|
||||
condition
|
||||
|
||||
@@ -732,6 +732,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
||||
)
|
||||
amounts["pending_accrual_entries"] = pending_accrual_entries
|
||||
amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
|
||||
amounts["written_off_amount"] = flt(against_loan_doc.written_off_amount, precision)
|
||||
|
||||
if final_due_date:
|
||||
amounts["due_date"] = final_due_date
|
||||
|
||||
@@ -57,7 +57,7 @@ def process_loan_interest_accrual_for_demand_loans(
|
||||
|
||||
def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None):
|
||||
|
||||
if not term_loan_accrual_pending(posting_date or nowdate()):
|
||||
if not term_loan_accrual_pending(posting_date or nowdate(), loan=loan):
|
||||
return
|
||||
|
||||
loan_process = frappe.new_doc("Process Loan Interest Accrual")
|
||||
@@ -71,9 +71,12 @@ def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=No
|
||||
return loan_process.name
|
||||
|
||||
|
||||
def term_loan_accrual_pending(date):
|
||||
pending_accrual = frappe.db.get_value(
|
||||
"Repayment Schedule", {"payment_date": ("<=", date), "is_accrued": 0}
|
||||
)
|
||||
def term_loan_accrual_pending(date, loan=None):
|
||||
filters = {"payment_date": ("<=", date), "is_accrued": 0}
|
||||
|
||||
if loan:
|
||||
filters.update({"parent": loan})
|
||||
|
||||
pending_accrual = frappe.db.get_value("Repayment Schedule", filters)
|
||||
|
||||
return pending_accrual
|
||||
|
||||
@@ -415,7 +415,7 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
|
||||
},
|
||||
"Maintenance Schedule Item": {
|
||||
"doctype": "Maintenance Visit Purpose",
|
||||
"condition": lambda doc: doc.item_name == item_name,
|
||||
"condition": lambda doc: doc.item_name == item_name if item_name else True,
|
||||
"field_map": {"sales_person": "service_person"},
|
||||
"postprocess": update_serial,
|
||||
},
|
||||
|
||||
@@ -189,8 +189,8 @@ class BOM(WebsiteGenerator):
|
||||
self.validate_transfer_against()
|
||||
self.set_routing_operations()
|
||||
self.validate_operations()
|
||||
self.update_exploded_items(save=False)
|
||||
self.calculate_cost()
|
||||
self.update_exploded_items(save=False)
|
||||
self.update_stock_qty()
|
||||
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
|
||||
self.validate_scrap_items()
|
||||
|
||||
@@ -611,6 +611,34 @@ class TestBOM(FrappeTestCase):
|
||||
bom.reload()
|
||||
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
|
||||
|
||||
def test_exploded_items_rate(self):
|
||||
rm_item = make_item(
|
||||
properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
|
||||
).name
|
||||
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
|
||||
bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_save=True)
|
||||
|
||||
bom.rm_cost_as_per = "Last Purchase Rate"
|
||||
bom.save()
|
||||
self.assertEqual(bom.items[0].base_rate, 89)
|
||||
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
|
||||
|
||||
bom.rm_cost_as_per = "Price List"
|
||||
bom.save()
|
||||
self.assertEqual(bom.items[0].base_rate, 0.0)
|
||||
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
|
||||
|
||||
bom.rm_cost_as_per = "Valuation Rate"
|
||||
bom.save()
|
||||
self.assertEqual(bom.items[0].base_rate, 99)
|
||||
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
|
||||
|
||||
bom.submit()
|
||||
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
|
||||
|
||||
|
||||
def get_default_bom(item_code="_Test FG Item 2"):
|
||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||
|
||||
@@ -184,6 +184,7 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"options": "currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -288,7 +289,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-19 02:32:43.785470",
|
||||
"modified": "2022-07-28 10:20:51.559010",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Item",
|
||||
|
||||
@@ -482,7 +482,6 @@ class ProductionPlan(Document):
|
||||
"bom_no",
|
||||
"stock_uom",
|
||||
"bom_level",
|
||||
"production_plan_item",
|
||||
"schedule_date",
|
||||
]:
|
||||
if row.get(field):
|
||||
@@ -639,6 +638,9 @@ class ProductionPlan(Document):
|
||||
sub_assembly_items_store = [] # temporary store to process all subassembly items
|
||||
|
||||
for row in self.po_items:
|
||||
if not row.item_code:
|
||||
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
|
||||
|
||||
bom_data = []
|
||||
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
|
||||
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
|
||||
@@ -654,6 +656,8 @@ class ProductionPlan(Document):
|
||||
row.idx = idx + 1
|
||||
self.append("sub_assembly_items", row)
|
||||
|
||||
self.set_default_supplier_for_subcontracting_order()
|
||||
|
||||
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
|
||||
"Modify bom_data, set additional details."
|
||||
for data in bom_data:
|
||||
@@ -665,6 +669,32 @@ class ProductionPlan(Document):
|
||||
"Subcontract" if data.is_sub_contracted_item else "In House"
|
||||
)
|
||||
|
||||
def set_default_supplier_for_subcontracting_order(self):
|
||||
items = [
|
||||
d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract"
|
||||
]
|
||||
|
||||
if not items:
|
||||
return
|
||||
|
||||
default_supplier = frappe._dict(
|
||||
frappe.get_all(
|
||||
"Item Default",
|
||||
fields=["parent", "default_supplier"],
|
||||
filters={"parent": ("in", items), "default_supplier": ("is", "set")},
|
||||
as_list=1,
|
||||
)
|
||||
)
|
||||
|
||||
if not default_supplier:
|
||||
return
|
||||
|
||||
for row in self.sub_assembly_items:
|
||||
if row.type_of_manufacturing != "Subcontract":
|
||||
continue
|
||||
|
||||
row.supplier = default_supplier.get(row.production_item)
|
||||
|
||||
def combine_subassembly_items(self, sub_assembly_items_store):
|
||||
"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
|
||||
key_wise_data = {}
|
||||
|
||||
@@ -11,8 +11,9 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
||||
get_warehouse_list,
|
||||
)
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
@@ -280,6 +281,31 @@ class TestProductionPlan(FrappeTestCase):
|
||||
pln.reload()
|
||||
pln.cancel()
|
||||
|
||||
def test_production_plan_subassembly_default_supplier(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
bom_tree_1 = {"Test Laptop": {"Test Motherboard": {"Test Motherboard Wires": {}}}}
|
||||
bom = create_nested_bom(bom_tree_1, prefix="")
|
||||
|
||||
item_doc = frappe.get_doc("Item", "Test Motherboard")
|
||||
company = "_Test Company"
|
||||
|
||||
item_doc.is_sub_contracted_item = 1
|
||||
for row in item_doc.item_defaults:
|
||||
if row.company == company and not row.default_supplier:
|
||||
row.default_supplier = "_Test Supplier"
|
||||
|
||||
if not item_doc.item_defaults:
|
||||
item_doc.append("item_defaults", {"company": company, "default_supplier": "_Test Supplier"})
|
||||
|
||||
item_doc.save()
|
||||
|
||||
plan = create_production_plan(item_code="Test Laptop", use_multi_level_bom=1, do_not_submit=True)
|
||||
plan.get_sub_assembly_items()
|
||||
plan.set_default_supplier_for_subcontracting_order()
|
||||
|
||||
self.assertEqual(plan.sub_assembly_items[0].supplier, "_Test Supplier")
|
||||
|
||||
def test_production_plan_combine_subassembly(self):
|
||||
"""
|
||||
Test combining Sub assembly items belonging to the same BOM in Prod Plan.
|
||||
@@ -583,9 +609,6 @@ class TestProductionPlan(FrappeTestCase):
|
||||
Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel)
|
||||
"""
|
||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
make_stock_entry as make_se_from_wo,
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
|
||||
@@ -629,9 +652,6 @@ class TestProductionPlan(FrappeTestCase):
|
||||
def test_production_plan_pending_qty_independent_items(self):
|
||||
"Test Prod Plan impact if items are added independently (no from SO or MR)."
|
||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
make_stock_entry as make_se_from_wo,
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
|
||||
@@ -728,6 +748,57 @@ class TestProductionPlan(FrappeTestCase):
|
||||
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
|
||||
self.assertEqual(po_item.name, subassy_item.production_plan_item)
|
||||
|
||||
def test_produced_qty_for_multi_level_bom_item(self):
|
||||
# Create Items and BOMs
|
||||
rm_item = make_item(properties={"is_stock_item": 1}).name
|
||||
sub_assembly_item = make_item(properties={"is_stock_item": 1}).name
|
||||
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||
|
||||
make_stock_entry(
|
||||
item_code=rm_item,
|
||||
qty=60,
|
||||
to_warehouse="Work In Progress - _TC",
|
||||
rate=99,
|
||||
purpose="Material Receipt",
|
||||
)
|
||||
|
||||
make_bom(item=sub_assembly_item, raw_materials=[rm_item], rm_qty=3)
|
||||
make_bom(item=fg_item, raw_materials=[sub_assembly_item], rm_qty=4)
|
||||
|
||||
# Step - 1: Create Production Plan
|
||||
pln = create_production_plan(item_code=fg_item, planned_qty=5, skip_getting_mr_items=1)
|
||||
pln.get_sub_assembly_items()
|
||||
|
||||
# Step - 2: Create Work Orders
|
||||
pln.make_work_order()
|
||||
work_orders = frappe.get_all("Work Order", filters={"production_plan": pln.name}, pluck="name")
|
||||
sa_wo = fg_wo = None
|
||||
for work_order in work_orders:
|
||||
wo_doc = frappe.get_doc("Work Order", work_order)
|
||||
if wo_doc.production_plan_item:
|
||||
wo_doc.update(
|
||||
{"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
|
||||
)
|
||||
fg_wo = wo_doc.name
|
||||
else:
|
||||
wo_doc.update(
|
||||
{"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Work In Progress - _TC"}
|
||||
)
|
||||
sa_wo = wo_doc.name
|
||||
wo_doc.submit()
|
||||
|
||||
# Step - 3: Complete Work Orders
|
||||
se = frappe.get_doc(make_se_from_wo(sa_wo, "Manufacture"))
|
||||
se.submit()
|
||||
|
||||
se = frappe.get_doc(make_se_from_wo(fg_wo, "Manufacture"))
|
||||
se.submit()
|
||||
|
||||
# Step - 4: Check Production Plan Item Produced Qty
|
||||
pln.load_from_db()
|
||||
self.assertEqual(pln.status, "Completed")
|
||||
self.assertEqual(pln.po_items[0].produced_qty, 5)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -26,6 +26,8 @@ from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.utils import get_bin
|
||||
|
||||
test_dependencies = ["BOM"]
|
||||
|
||||
|
||||
class TestWorkOrder(FrappeTestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -7,6 +7,6 @@ def get_data():
|
||||
"non_standard_fieldnames": {"Batch": "reference_name"},
|
||||
"transactions": [
|
||||
{"label": _("Transactions"), "items": ["Stock Entry", "Job Card", "Pick List"]},
|
||||
{"label": _("Reference"), "items": ["Serial No", "Batch"]},
|
||||
{"label": _("Reference"), "items": ["Serial No", "Batch", "Material Request"]},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -268,6 +268,7 @@ erpnext.patches.v13_0.enable_ksa_vat_docs #1
|
||||
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
|
||||
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
|
||||
erpnext.patches.v13_0.reset_corrupt_defaults
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
|
||||
|
||||
[post_model_sync]
|
||||
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
||||
@@ -308,4 +309,7 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
erpnext.patches.v14_0.crm_ux_cleanup
|
||||
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
|
||||
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
|
||||
erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
|
||||
erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
|
||||
erpnext.patches.v14_0.fix_crm_no_of_employees
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
||||
|
||||
@@ -14,7 +14,8 @@ def execute():
|
||||
|
||||
for sla in frappe.get_all("Service Level Agreement"):
|
||||
agreement = frappe.get_doc("Service Level Agreement", sla.name)
|
||||
agreement.document_type = "Issue"
|
||||
agreement.db_set("document_type", "Issue")
|
||||
agreement.reload()
|
||||
agreement.apply_sla_for_resolution = 1
|
||||
agreement.append("sla_fulfilled_on", {"status": "Resolved"})
|
||||
agreement.append("sla_fulfilled_on", {"status": "Closed"})
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
|
||||
def execute():
|
||||
accounting_dimensions = frappe.db.get_all(
|
||||
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
|
||||
)
|
||||
|
||||
if not accounting_dimensions:
|
||||
return
|
||||
|
||||
for d in accounting_dimensions:
|
||||
doctype = "Asset Repair"
|
||||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
|
||||
|
||||
if field:
|
||||
continue
|
||||
|
||||
df = {
|
||||
"fieldname": d.fieldname,
|
||||
"label": d.label,
|
||||
"fieldtype": "Link",
|
||||
"options": d.document_type,
|
||||
"insert_after": "accounting_dimensions_section",
|
||||
}
|
||||
|
||||
create_custom_field(doctype, df, ignore_validate=True)
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
@@ -16,18 +16,18 @@ def execute():
|
||||
delete_auto_email_reports(report)
|
||||
check_and_delete_linked_reports(report)
|
||||
|
||||
frappe.delete_doc("Report", report)
|
||||
frappe.delete_doc("Report", report, force=True)
|
||||
|
||||
|
||||
def delete_auto_email_reports(report):
|
||||
"""Check for one or multiple Auto Email Reports and delete"""
|
||||
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
|
||||
for auto_email_report in auto_email_reports:
|
||||
frappe.delete_doc("Auto Email Report", auto_email_report[0])
|
||||
frappe.delete_doc("Auto Email Report", auto_email_report[0], force=True)
|
||||
|
||||
|
||||
def delete_links_from_desktop_icons(report):
|
||||
"""Check for one or multiple Desktop Icons and delete"""
|
||||
desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
|
||||
for desktop_icon in desktop_icons:
|
||||
frappe.delete_doc("Desktop Icon", desktop_icon[0])
|
||||
frappe.delete_doc("Desktop Icon", desktop_icon[0], force=True)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
|
||||
def execute():
|
||||
accounting_dimensions = frappe.db.get_all(
|
||||
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
|
||||
)
|
||||
|
||||
if not accounting_dimensions:
|
||||
return
|
||||
|
||||
count = 1
|
||||
for d in accounting_dimensions:
|
||||
|
||||
if count % 2 == 0:
|
||||
insert_after_field = "dimension_col_break"
|
||||
else:
|
||||
insert_after_field = "accounting_dimensions_section"
|
||||
|
||||
for doctype in [
|
||||
"Subcontracting Order",
|
||||
"Subcontracting Order Item",
|
||||
"Subcontracting Receipt",
|
||||
"Subcontracting Receipt Item",
|
||||
]:
|
||||
|
||||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
|
||||
|
||||
if field:
|
||||
continue
|
||||
|
||||
df = {
|
||||
"fieldname": d.fieldname,
|
||||
"label": d.label,
|
||||
"fieldtype": "Link",
|
||||
"options": d.document_type,
|
||||
"insert_after": insert_after_field,
|
||||
}
|
||||
|
||||
try:
|
||||
create_custom_field(doctype, df, ignore_validate=True)
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
count += 1
|
||||
26
erpnext/patches/v14_0/fix_crm_no_of_employees.py
Normal file
26
erpnext/patches/v14_0/fix_crm_no_of_employees.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
options = {
|
||||
"11-20": "11-50",
|
||||
"21-30": "11-50",
|
||||
"31-100": "51-200",
|
||||
"101-500": "201-500",
|
||||
"500-1000": "501-1000",
|
||||
">1000": "1000+",
|
||||
}
|
||||
|
||||
for doctype in ("Lead", "Opportunity", "Prospect"):
|
||||
frappe.reload_doctype(doctype)
|
||||
for key, value in options.items():
|
||||
frappe.db.sql(
|
||||
"""
|
||||
update `tab{doctype}`
|
||||
set no_of_employees = %s
|
||||
where no_of_employees = %s
|
||||
""".format(
|
||||
doctype=doctype
|
||||
),
|
||||
(value, key),
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.utils import create_batch
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
|
||||
|
||||
gle = qb.DocType("GL Entry")
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
# get ple and their remarks from GL Entry
|
||||
pl_entries = (
|
||||
qb.from_(ple)
|
||||
.left_join(gle)
|
||||
.on(
|
||||
(ple.account == gle.account)
|
||||
& (ple.party_type == gle.party_type)
|
||||
& (ple.party == gle.party)
|
||||
& (ple.voucher_type == gle.voucher_type)
|
||||
& (ple.voucher_no == gle.voucher_no)
|
||||
& (ple.company == gle.company)
|
||||
)
|
||||
.select(
|
||||
ple.company,
|
||||
ple.account,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
gle.remarks.as_("gle_remarks"),
|
||||
)
|
||||
.where((ple.delinked == 0) & (gle.is_cancelled == 0))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
if pl_entries:
|
||||
# split into multiple batches, update and commit for each batch
|
||||
batch_size = 1000
|
||||
for batch in create_batch(pl_entries, batch_size):
|
||||
for entry in batch:
|
||||
query = (
|
||||
qb.update(ple)
|
||||
.set(ple.remarks, entry.gle_remarks)
|
||||
.where(
|
||||
(ple.company == entry.company)
|
||||
& (ple.account == entry.account)
|
||||
& (ple.party_type == entry.party_type)
|
||||
& (ple.party == entry.party)
|
||||
& (ple.voucher_type == entry.voucher_type)
|
||||
& (ple.voucher_no == entry.voucher_no)
|
||||
)
|
||||
)
|
||||
query.run()
|
||||
|
||||
frappe.db.commit()
|
||||
@@ -1,3 +1,4 @@
|
||||
import click
|
||||
import frappe
|
||||
from frappe.utils import flt
|
||||
|
||||
@@ -16,6 +17,19 @@ def execute():
|
||||
for opportunity in opportunities:
|
||||
company_currency = erpnext.get_company_currency(opportunity.company)
|
||||
|
||||
if opportunity.currency is None or opportunity.currency == "":
|
||||
opportunity.currency = company_currency
|
||||
frappe.db.set_value(
|
||||
"Opportunity",
|
||||
opportunity.name,
|
||||
{"currency": opportunity.currency},
|
||||
update_modified=False,
|
||||
)
|
||||
click.secho(
|
||||
f' Opportunity `{opportunity.name}` has no currency set. Setting it to company currency as default: `{opportunity.currency}`"\n',
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
# base total and total will be 0 only since item table did not have amount field earlier
|
||||
if opportunity.currency != company_currency:
|
||||
conversion_rate = get_exchange_rate(opportunity.currency, company_currency)
|
||||
|
||||
@@ -57,7 +57,11 @@ class TestHomepageSection(unittest.TestCase):
|
||||
self.assertEqual(cards[0].h5.text, "Card 1")
|
||||
self.assertEqual(cards[0].a["href"], "/card-1")
|
||||
self.assertEqual(cards[1].p.text, "Subtitle 2")
|
||||
self.assertEqual(cards[1].find(class_="website-image-lazy")["data-src"], "test.jpg")
|
||||
|
||||
img = cards[1].find(class_="card-img-top")
|
||||
|
||||
self.assertEqual(img["src"], "test.jpg")
|
||||
self.assertEqual(img["loading"], "lazy")
|
||||
|
||||
# cleanup
|
||||
frappe.db.rollback()
|
||||
|
||||
@@ -379,7 +379,7 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters):
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
|
||||
(case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end)
|
||||
(case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end),
|
||||
idx desc,
|
||||
name, full_name
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
|
||||
@@ -1,127 +1,70 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "Prompt",
|
||||
"beta": 0,
|
||||
"creation": "2019-04-19 15:04:05.317138",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"weight",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "weight",
|
||||
"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": "Weight",
|
||||
"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
|
||||
"label": "Weight"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "description",
|
||||
"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": "Description",
|
||||
"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
|
||||
"label": "Description"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-04-19 15:31:48.080164",
|
||||
"links": [],
|
||||
"modified": "2022-08-29 17:46:41.342979",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Task Type",
|
||||
"name_case": "",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Projects Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Projects User",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -39,6 +39,12 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this._calculate_taxes_and_totals();
|
||||
this.calculate_discount_amount();
|
||||
|
||||
// # Update grand total as per cash and non trade discount
|
||||
if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) {
|
||||
this.frm.doc.grand_total -= this.frm.doc.discount_amount;
|
||||
this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount;
|
||||
}
|
||||
|
||||
await this.calculate_shipping_charges();
|
||||
|
||||
// Advance calculation applicable to Sales /Purchase Invoice
|
||||
@@ -633,6 +639,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.frm.doc.base_discount_amount = flt(this.frm.doc.discount_amount * this.frm.doc.conversion_rate,
|
||||
precision("base_discount_amount"));
|
||||
|
||||
if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) {
|
||||
return;
|
||||
}
|
||||
|
||||
var total_for_discount_amount = this.get_total_for_discount_amount();
|
||||
var net_total = 0;
|
||||
// calculate item amount after Discount Amount
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide('erpnext.accounts.dimensions');
|
||||
|
||||
erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals {
|
||||
setup() {
|
||||
@@ -794,24 +793,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
set_party_account(set_pricing);
|
||||
});
|
||||
|
||||
// Get default company billing address in Purchase Invoice, Order and Receipt
|
||||
if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) {
|
||||
frappe.call({
|
||||
method: "erpnext.setup.doctype.company.company.get_default_company_address",
|
||||
args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""},
|
||||
debounce: 2000,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
me.frm.set_value("billing_address", r.message);
|
||||
} else {
|
||||
if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) {
|
||||
me.frm.set_value("company_address", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
set_party_account(set_pricing);
|
||||
}
|
||||
|
||||
@@ -3,25 +3,14 @@
|
||||
|
||||
frappe.provide("erpnext.utils");
|
||||
|
||||
const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
|
||||
const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
|
||||
|
||||
erpnext.utils.get_party_details = function(frm, method, args, callback) {
|
||||
if (!method) {
|
||||
method = "erpnext.accounts.party.get_party_details";
|
||||
}
|
||||
|
||||
if (args) {
|
||||
if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
|
||||
if (frm.doc.company_address && (!args.company_address)) {
|
||||
args.company_address = frm.doc.company_address;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
|
||||
if (frm.doc.shipping_address && (!args.shipping_address)) {
|
||||
args.shipping_address = frm.doc.shipping_address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!args) {
|
||||
if ((frm.doctype != "Purchase Order" && frm.doc.customer)
|
||||
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
|
||||
@@ -45,41 +34,44 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
|
||||
};
|
||||
}
|
||||
|
||||
if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
|
||||
if (!args) {
|
||||
if (!args) {
|
||||
if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
|
||||
args = {
|
||||
party: frm.doc.customer || frm.doc.party_name,
|
||||
party_type: 'Customer'
|
||||
}
|
||||
}
|
||||
if (frm.doc.company_address && (!args.company_address)) {
|
||||
args.company_address = frm.doc.company_address;
|
||||
};
|
||||
}
|
||||
|
||||
if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) {
|
||||
args.shipping_address_name = frm.doc.shipping_address_name;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
|
||||
if (!args) {
|
||||
if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
|
||||
args = {
|
||||
party: frm.doc.supplier,
|
||||
party_type: 'Supplier'
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.doc.shipping_address && (!args.shipping_address)) {
|
||||
args.shipping_address = frm.doc.shipping_address;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (args) {
|
||||
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
|
||||
args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
|
||||
if (!args || !args.party) return;
|
||||
|
||||
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
|
||||
args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
|
||||
}
|
||||
|
||||
if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
|
||||
if (!args.company_address && frm.doc.company_address) {
|
||||
args.company_address = frm.doc.company_address;
|
||||
}
|
||||
}
|
||||
if (!args || !args.party) return;
|
||||
|
||||
if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
|
||||
if (!args.company_address && frm.doc.billing_address) {
|
||||
args.company_address = frm.doc.billing_address;
|
||||
}
|
||||
|
||||
if (!args.shipping_address && frm.doc.shipping_address) {
|
||||
args.shipping_address = frm.doc.shipping_address;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
|
||||
if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
|
||||
|
||||
@@ -177,16 +177,16 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype):
|
||||
"parent": invoice.name,
|
||||
"item_tax_template": vat_setting.item_tax_template,
|
||||
},
|
||||
fields=["item_code", "net_amount"],
|
||||
fields=["item_code", "base_net_amount"],
|
||||
)
|
||||
|
||||
for item in invoice_items:
|
||||
# Summing up total taxable amount
|
||||
if invoice.is_return == 0:
|
||||
total_taxable_amount += item.net_amount
|
||||
total_taxable_amount += item.base_net_amount
|
||||
|
||||
if invoice.is_return == 1:
|
||||
total_taxable_adjustment_amount += item.net_amount
|
||||
total_taxable_adjustment_amount += item.base_net_amount
|
||||
|
||||
# Summing up total tax
|
||||
total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name)
|
||||
|
||||
@@ -268,7 +268,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||
|
||||
def set_expired_status():
|
||||
# filter out submitted non expired quotations whose validity has been ended
|
||||
cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status != 'Expired' and `tabQuotation`.valid_till < %s"
|
||||
cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status NOT IN ('Expired', 'Lost') and `tabQuotation`.valid_till < %s"
|
||||
# check if those QUO have SO against it
|
||||
so_against_quo = """
|
||||
SELECT
|
||||
|
||||
@@ -892,6 +892,7 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
|
||||
target.additional_discount_percentage = 0.0
|
||||
target.discount_amount = 0.0
|
||||
target.inter_company_order_reference = ""
|
||||
target.shipping_rule = ""
|
||||
|
||||
default_price_list = frappe.get_value("Supplier", supplier, "default_price_list")
|
||||
if default_price_list:
|
||||
@@ -1010,6 +1011,7 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
||||
target.additional_discount_percentage = 0.0
|
||||
target.discount_amount = 0.0
|
||||
target.inter_company_order_reference = ""
|
||||
target.shipping_rule = ""
|
||||
target.customer = ""
|
||||
target.customer_name = ""
|
||||
target.run_method("set_missing_values")
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
"depreciation_expense_account",
|
||||
"series_for_depreciation_entry",
|
||||
"expenses_included_in_asset_valuation",
|
||||
"repair_and_maintenance_account",
|
||||
"column_break_40",
|
||||
"disposal_account",
|
||||
"depreciation_cost_center",
|
||||
@@ -234,7 +233,6 @@
|
||||
"label": "Default Warehouse for Sales Return",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "country",
|
||||
"fieldtype": "Link",
|
||||
@@ -678,12 +676,6 @@
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fixed Asset Defaults"
|
||||
},
|
||||
{
|
||||
"fieldname": "repair_and_maintenance_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Repair and Maintenance Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_28",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -709,7 +701,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-30 18:03:18.701314",
|
||||
"modified": "2022-08-16 16:09:02.327724",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
||||
@@ -10,79 +10,89 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"basic_details_tab",
|
||||
"basic_information",
|
||||
"employee",
|
||||
"naming_series",
|
||||
"first_name",
|
||||
"middle_name",
|
||||
"last_name",
|
||||
"salutation",
|
||||
"employee_name",
|
||||
"image",
|
||||
"column_break1",
|
||||
"company",
|
||||
"status",
|
||||
"column_break_9",
|
||||
"gender",
|
||||
"date_of_birth",
|
||||
"salutation",
|
||||
"column_break1",
|
||||
"date_of_joining",
|
||||
"employee_number",
|
||||
"emergency_contact_details",
|
||||
"person_to_be_contacted",
|
||||
"relation",
|
||||
"column_break_19",
|
||||
"emergency_phone_number",
|
||||
"image",
|
||||
"status",
|
||||
"erpnext_user",
|
||||
"user_id",
|
||||
"create_user",
|
||||
"create_user_permission",
|
||||
"employment_details",
|
||||
"scheduled_confirmation_date",
|
||||
"final_confirmation_date",
|
||||
"col_break_22",
|
||||
"contract_end_date",
|
||||
"notice_number_of_days",
|
||||
"date_of_retirement",
|
||||
"job_profile",
|
||||
"company_details_section",
|
||||
"company",
|
||||
"department",
|
||||
"employee_number",
|
||||
"column_break_25",
|
||||
"designation",
|
||||
"reports_to",
|
||||
"column_break_31",
|
||||
"column_break_18",
|
||||
"branch",
|
||||
"employment_details",
|
||||
"scheduled_confirmation_date",
|
||||
"column_break_32",
|
||||
"final_confirmation_date",
|
||||
"contract_end_date",
|
||||
"col_break_22",
|
||||
"notice_number_of_days",
|
||||
"date_of_retirement",
|
||||
"contact_details",
|
||||
"cell_number",
|
||||
"column_break_40",
|
||||
"personal_email",
|
||||
"company_email",
|
||||
"column_break4",
|
||||
"prefered_contact_email",
|
||||
"prefered_email",
|
||||
"unsubscribed",
|
||||
"address_section",
|
||||
"current_address",
|
||||
"current_accommodation_type",
|
||||
"column_break_46",
|
||||
"permanent_address",
|
||||
"permanent_accommodation_type",
|
||||
"emergency_contact_details",
|
||||
"person_to_be_contacted",
|
||||
"column_break_55",
|
||||
"emergency_phone_number",
|
||||
"column_break_19",
|
||||
"relation",
|
||||
"attendance_and_leave_details",
|
||||
"attendance_device_id",
|
||||
"column_break_44",
|
||||
"holiday_list",
|
||||
"salary_information",
|
||||
"salary_currency",
|
||||
"ctc",
|
||||
"payroll_cost_center",
|
||||
"column_break_52",
|
||||
"salary_currency",
|
||||
"salary_mode",
|
||||
"bank_details_section",
|
||||
"bank_name",
|
||||
"bank_ac_no",
|
||||
"contact_details",
|
||||
"cell_number",
|
||||
"prefered_email",
|
||||
"personal_email",
|
||||
"unsubscribed",
|
||||
"permanent_accommodation_type",
|
||||
"permanent_address",
|
||||
"column_break4",
|
||||
"prefered_contact_email",
|
||||
"company_email",
|
||||
"current_accommodation_type",
|
||||
"current_address",
|
||||
"sb53",
|
||||
"bio",
|
||||
"personal_details",
|
||||
"passport_number",
|
||||
"date_of_issue",
|
||||
"valid_upto",
|
||||
"place_of_issue",
|
||||
"marital_status",
|
||||
"blood_group",
|
||||
"column_break6",
|
||||
"family_background",
|
||||
"column_break6",
|
||||
"blood_group",
|
||||
"health_details",
|
||||
"passport_details_section",
|
||||
"passport_number",
|
||||
"valid_upto",
|
||||
"column_break_73",
|
||||
"date_of_issue",
|
||||
"place_of_issue",
|
||||
"profile_tab",
|
||||
"bio",
|
||||
"educational_qualification",
|
||||
"education",
|
||||
"previous_work_experience",
|
||||
@@ -92,16 +102,20 @@
|
||||
"exit",
|
||||
"resignation_letter_date",
|
||||
"relieving_date",
|
||||
"reason_for_leaving",
|
||||
"leave_encashed",
|
||||
"encashment_date",
|
||||
"exit_interview_details",
|
||||
"held_on",
|
||||
"new_workplace",
|
||||
"column_break_99",
|
||||
"leave_encashed",
|
||||
"encashment_date",
|
||||
"feedback_section",
|
||||
"reason_for_leaving",
|
||||
"column_break_104",
|
||||
"feedback",
|
||||
"lft",
|
||||
"rgt",
|
||||
"old_parent"
|
||||
"old_parent",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -261,7 +275,7 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "erpnext_user",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "ERPNext User"
|
||||
"label": "User Details"
|
||||
},
|
||||
{
|
||||
"description": "System User (login) ID. If set, it will become default for all HR forms.",
|
||||
@@ -289,8 +303,8 @@
|
||||
"allow_in_quick_entry": 1,
|
||||
"collapsible": 1,
|
||||
"fieldname": "employment_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Joining Details"
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Joining"
|
||||
},
|
||||
{
|
||||
"fieldname": "scheduled_confirmation_date",
|
||||
@@ -331,12 +345,6 @@
|
||||
"oldfieldname": "date_of_retirement",
|
||||
"oldfieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "job_profile",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Department"
|
||||
},
|
||||
{
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
@@ -366,10 +374,6 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Employee"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_31",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "branch",
|
||||
"fieldtype": "Link",
|
||||
@@ -391,7 +395,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "salary_information",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Salary Details",
|
||||
"oldfieldtype": "Section Break",
|
||||
"width": "50%"
|
||||
@@ -423,8 +427,8 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "contact_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Contact Details"
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "cell_number",
|
||||
@@ -493,12 +497,6 @@
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Current Address"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sb53",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Personal Bio"
|
||||
},
|
||||
{
|
||||
"description": "Short biography for website and other publications.",
|
||||
"fieldname": "bio",
|
||||
@@ -508,7 +506,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "personal_details",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Personal Details"
|
||||
},
|
||||
{
|
||||
@@ -601,7 +599,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "exit",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Exit",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
@@ -702,7 +700,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "attendance_and_leave_details",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Attendance and Leave Details"
|
||||
},
|
||||
{
|
||||
@@ -713,10 +711,6 @@
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_52",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "salary_currency",
|
||||
"fieldtype": "Link",
|
||||
@@ -728,13 +722,95 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Cost to Company (CTC)",
|
||||
"options": "salary_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "basic_details_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Basic Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Company Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "address_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_46",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "profile_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Profile"
|
||||
},
|
||||
{
|
||||
"fieldname": "passport_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Passport Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_73",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "bank_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Bank Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_25",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "connections_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_32",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_40",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_55",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_99",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "feedback_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Feedback"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_104",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2022-06-27 01:29:32.952091",
|
||||
"modified": "2022-08-23 13:47:46.944993",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Employee",
|
||||
|
||||
@@ -142,10 +142,6 @@ def get_item_for_list_in_html(context):
|
||||
if (context.get("website_image") or "").startswith("files/"):
|
||||
context["website_image"] = "/" + quote(context["website_image"])
|
||||
|
||||
context["show_availability_status"] = cint(
|
||||
frappe.db.get_single_value("E Commerce Settings", "show_availability_status")
|
||||
)
|
||||
|
||||
products_template = "templates/includes/products_as_list.html"
|
||||
|
||||
return frappe.get_template(products_template).render(context)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
|
||||
from frappe.utils import cint
|
||||
|
||||
@@ -83,35 +83,32 @@ def setup_currency_exchange():
|
||||
|
||||
|
||||
def create_print_setting_custom_fields():
|
||||
create_custom_field(
|
||||
"Print Settings",
|
||||
create_custom_fields(
|
||||
{
|
||||
"label": _("Compact Item Print"),
|
||||
"fieldname": "compact_item_print",
|
||||
"fieldtype": "Check",
|
||||
"default": 1,
|
||||
"insert_after": "with_letterhead",
|
||||
},
|
||||
)
|
||||
create_custom_field(
|
||||
"Print Settings",
|
||||
{
|
||||
"label": _("Print UOM after Quantity"),
|
||||
"fieldname": "print_uom_after_quantity",
|
||||
"fieldtype": "Check",
|
||||
"default": 0,
|
||||
"insert_after": "compact_item_print",
|
||||
},
|
||||
)
|
||||
create_custom_field(
|
||||
"Print Settings",
|
||||
{
|
||||
"label": _("Print taxes with zero amount"),
|
||||
"fieldname": "print_taxes_with_zero_amount",
|
||||
"fieldtype": "Check",
|
||||
"default": 0,
|
||||
"insert_after": "allow_print_for_cancelled",
|
||||
},
|
||||
"Print Settings": [
|
||||
{
|
||||
"label": _("Compact Item Print"),
|
||||
"fieldname": "compact_item_print",
|
||||
"fieldtype": "Check",
|
||||
"default": "1",
|
||||
"insert_after": "with_letterhead",
|
||||
},
|
||||
{
|
||||
"label": _("Print UOM after Quantity"),
|
||||
"fieldname": "print_uom_after_quantity",
|
||||
"fieldtype": "Check",
|
||||
"default": "0",
|
||||
"insert_after": "compact_item_print",
|
||||
},
|
||||
{
|
||||
"label": _("Print taxes with zero amount"),
|
||||
"fieldname": "print_taxes_with_zero_amount",
|
||||
"fieldtype": "Check",
|
||||
"default": "0",
|
||||
"insert_after": "allow_print_for_cancelled",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -473,7 +473,13 @@ def make_new_batch(**args):
|
||||
"doctype": "Batch",
|
||||
"batch_id": args.batch_id,
|
||||
"item": args.item_code,
|
||||
"expiry_date": args.expiry_date,
|
||||
}
|
||||
).insert()
|
||||
)
|
||||
|
||||
if args.expiry_date:
|
||||
batch.expiry_date = args.expiry_date
|
||||
|
||||
batch.insert()
|
||||
|
||||
return batch
|
||||
|
||||
@@ -30,19 +30,69 @@ frappe.ui.form.on('Inventory Dimension', {
|
||||
|
||||
onload(frm) {
|
||||
frm.trigger('render_traget_field');
|
||||
frm.trigger("set_parent_fields");
|
||||
},
|
||||
|
||||
refresh(frm) {
|
||||
if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger
|
||||
&& frm.doc.__onload.has_stock_ledger.length) {
|
||||
let msg = __('Stock transactions exists against this dimension, user can not update document.');
|
||||
frm.dashboard.add_comment(msg, 'blue', true);
|
||||
let allow_to_edit_fields = ['disabled', 'fetch_from_parent',
|
||||
'type_of_transaction', 'condition'];
|
||||
|
||||
frm.fields.forEach((field) => {
|
||||
if (field.df.fieldname !== 'disabled') {
|
||||
if (!in_list(allow_to_edit_fields, field.df.fieldname)) {
|
||||
frm.set_df_property(field.df.fieldname, "read_only", "1");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__('Delete Dimension'), () => {
|
||||
frm.trigger('delete_dimension');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
document_type(frm) {
|
||||
frm.trigger("set_parent_fields");
|
||||
},
|
||||
|
||||
set_parent_fields(frm) {
|
||||
if (frm.doc.apply_to_all_doctypes) {
|
||||
frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document);
|
||||
} else if (frm.doc.document_type && frm.doc.istable) {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields',
|
||||
args: {
|
||||
child_doctype: frm.doc.document_type,
|
||||
dimension_name: frm.doc.reference_document
|
||||
},
|
||||
callback: (r) => {
|
||||
if (r.message && r.message.length) {
|
||||
frm.set_df_property("fetch_from_parent", "options",
|
||||
[""].concat(r.message));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
delete_dimension(frm) {
|
||||
let msg = (`
|
||||
Custom fields related to this dimension will be deleted on deletion of dimension.
|
||||
<br> Do you want to delete {0} dimension?
|
||||
`);
|
||||
|
||||
frappe.confirm(__(msg, [frm.doc.name.bold()]), () => {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.delete_dimension',
|
||||
args: {
|
||||
dimension: frm.doc.name
|
||||
},
|
||||
callback: function() {
|
||||
frappe.set_route('List', 'Inventory Dimension');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:dimension_name",
|
||||
"creation": "2022-06-17 13:04:16.554051",
|
||||
"doctype": "DocType",
|
||||
@@ -22,6 +21,7 @@
|
||||
"document_type",
|
||||
"istable",
|
||||
"type_of_transaction",
|
||||
"fetch_from_parent",
|
||||
"column_break_16",
|
||||
"condition",
|
||||
"applicable_condition_example_section",
|
||||
@@ -101,12 +101,14 @@
|
||||
"fieldname": "target_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"label": "Target Fieldname (Stock Ledger Entry)",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "source_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"label": "Source Fieldname",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -123,7 +125,7 @@
|
||||
"fieldname": "type_of_transaction",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type of Transaction",
|
||||
"options": "\nInward\nOutward"
|
||||
"options": "\nInward\nOutward\nBoth"
|
||||
},
|
||||
{
|
||||
"fieldname": "html_19",
|
||||
@@ -140,11 +142,17 @@
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Set fieldname or DocType name like Supplier, Customer etc.",
|
||||
"fieldname": "fetch_from_parent",
|
||||
"fieldtype": "Select",
|
||||
"label": "Fetch Value From Parent Form"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-07-19 21:06:11.824976",
|
||||
"modified": "2022-09-02 13:29:04.098469",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Inventory Dimension",
|
||||
|
||||
@@ -43,13 +43,37 @@ class InventoryDimension(Document):
|
||||
return
|
||||
|
||||
old_doc = self._doc_before_save
|
||||
allow_to_edit_fields = [
|
||||
"disabled",
|
||||
"fetch_from_parent",
|
||||
"type_of_transaction",
|
||||
"condition",
|
||||
]
|
||||
|
||||
for field in frappe.get_meta("Inventory Dimension").fields:
|
||||
if field.fieldname != "disabled" and old_doc.get(field.fieldname) != self.get(field.fieldname):
|
||||
if field.fieldname not in allow_to_edit_fields and old_doc.get(field.fieldname) != self.get(
|
||||
field.fieldname
|
||||
):
|
||||
msg = f"""The user can not change value of the field {bold(field.label)} because
|
||||
stock transactions exists against the dimension {bold(self.name)}."""
|
||||
|
||||
frappe.throw(_(msg), DoNotChangeError)
|
||||
|
||||
def on_trash(self):
|
||||
self.delete_custom_fields()
|
||||
|
||||
def delete_custom_fields(self):
|
||||
filters = {"fieldname": self.source_fieldname}
|
||||
|
||||
if self.document_type:
|
||||
filters["dt"] = self.document_type
|
||||
|
||||
for field in frappe.get_all("Custom Field", filters=filters):
|
||||
frappe.delete_doc("Custom Field", field.name)
|
||||
|
||||
msg = f"Deleted custom fields related to the dimension {self.name}"
|
||||
frappe.msgprint(_(msg))
|
||||
|
||||
def reset_value(self):
|
||||
if self.apply_to_all_doctypes:
|
||||
self.istable = 0
|
||||
@@ -76,30 +100,35 @@ class InventoryDimension(Document):
|
||||
self.add_custom_fields()
|
||||
|
||||
def add_custom_fields(self):
|
||||
dimension_field = dict(
|
||||
fieldname=self.source_fieldname,
|
||||
fieldtype="Link",
|
||||
insert_after="warehouse",
|
||||
options=self.reference_document,
|
||||
label=self.dimension_name,
|
||||
)
|
||||
dimension_fields = [
|
||||
dict(
|
||||
fieldname="inventory_dimension",
|
||||
fieldtype="Section Break",
|
||||
insert_after="warehouse",
|
||||
label="Inventory Dimension",
|
||||
collapsible=1,
|
||||
),
|
||||
dict(
|
||||
fieldname=self.source_fieldname,
|
||||
fieldtype="Link",
|
||||
insert_after="inventory_dimension",
|
||||
options=self.reference_document,
|
||||
label=self.dimension_name,
|
||||
),
|
||||
]
|
||||
|
||||
custom_fields = {}
|
||||
|
||||
if self.apply_to_all_doctypes:
|
||||
for doctype in get_inventory_documents():
|
||||
if not frappe.db.get_value(
|
||||
"Custom Field", {"dt": doctype[0], "fieldname": self.source_fieldname}
|
||||
):
|
||||
custom_fields.setdefault(doctype[0], dimension_field)
|
||||
elif not frappe.db.get_value(
|
||||
"Custom Field", {"dt": self.document_type, "fieldname": self.source_fieldname}
|
||||
):
|
||||
custom_fields.setdefault(self.document_type, dimension_field)
|
||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||
else:
|
||||
custom_fields.setdefault(self.document_type, dimension_fields)
|
||||
|
||||
if not frappe.db.get_value(
|
||||
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
|
||||
):
|
||||
dimension_field = dimension_fields[1]
|
||||
dimension_field["fieldname"] = self.target_fieldname
|
||||
custom_fields["Stock Ledger Entry"] = dimension_field
|
||||
|
||||
@@ -143,7 +172,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
|
||||
elif (
|
||||
row.type_of_transaction == "Outward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Inward"
|
||||
else row.type_of_transaction != "Outward"
|
||||
) and sl_dict.actual_qty > 0:
|
||||
continue
|
||||
|
||||
@@ -166,7 +195,14 @@ def get_document_wise_inventory_dimensions(doctype) -> dict:
|
||||
if not frappe.local.document_wise_inventory_dimensions.get(doctype):
|
||||
dimensions = frappe.get_all(
|
||||
"Inventory Dimension",
|
||||
fields=["name", "source_fieldname", "condition", "target_fieldname", "type_of_transaction"],
|
||||
fields=[
|
||||
"name",
|
||||
"source_fieldname",
|
||||
"condition",
|
||||
"target_fieldname",
|
||||
"type_of_transaction",
|
||||
"fetch_from_parent",
|
||||
],
|
||||
filters={"disabled": 0},
|
||||
or_filters={"document_type": doctype, "apply_to_all_doctypes": 1},
|
||||
)
|
||||
@@ -194,3 +230,36 @@ def get_inventory_dimensions():
|
||||
frappe.local.inventory_dimensions = dimensions
|
||||
|
||||
return frappe.local.inventory_dimensions
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_dimension(dimension):
|
||||
doc = frappe.get_doc("Inventory Dimension", dimension)
|
||||
doc.delete()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_parent_fields(child_doctype, dimension_name):
|
||||
parent_doctypes = frappe.get_all(
|
||||
"DocField", fields=["parent"], filters={"options": child_doctype}
|
||||
)
|
||||
|
||||
fields = []
|
||||
|
||||
fields.extend(
|
||||
frappe.get_all(
|
||||
"DocField",
|
||||
fields=["fieldname as value", "label"],
|
||||
filters={"options": dimension_name, "parent": ("in", [d.parent for d in parent_doctypes])},
|
||||
)
|
||||
)
|
||||
|
||||
fields.extend(
|
||||
frappe.get_all(
|
||||
"Custom Field",
|
||||
fields=["fieldname as value", "label"],
|
||||
filters={"options": dimension_name, "dt": ("in", [d.parent for d in parent_doctypes])},
|
||||
)
|
||||
)
|
||||
|
||||
return fields
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
||||
CanNotBeChildDoc,
|
||||
CanNotBeDefaultDimension,
|
||||
DoNotChangeError,
|
||||
delete_dimension,
|
||||
)
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
@@ -42,6 +46,32 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
|
||||
self.assertRaises(CanNotBeDefaultDimension, inv_dim1.insert)
|
||||
|
||||
def test_delete_inventory_dimension(self):
|
||||
inv_dim1 = create_inventory_dimension(
|
||||
reference_document="Shelf",
|
||||
type_of_transaction="Outward",
|
||||
dimension_name="From Shelf",
|
||||
apply_to_all_doctypes=0,
|
||||
document_type="Stock Entry Detail",
|
||||
condition="parent.purpose == 'Material Issue'",
|
||||
)
|
||||
|
||||
inv_dim1.save()
|
||||
|
||||
custom_field = frappe.db.get_value(
|
||||
"Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name"
|
||||
)
|
||||
|
||||
self.assertTrue(custom_field)
|
||||
|
||||
delete_dimension(inv_dim1.name)
|
||||
|
||||
custom_field = frappe.db.get_value(
|
||||
"Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name"
|
||||
)
|
||||
|
||||
self.assertFalse(custom_field)
|
||||
|
||||
def test_inventory_dimension(self):
|
||||
warehouse = "Shelf Warehouse - _TC"
|
||||
item_code = "_Test Item"
|
||||
@@ -109,6 +139,58 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
self.assertTrue(inv_dim1.has_stock_ledger())
|
||||
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
||||
|
||||
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
|
||||
create_inventory_dimension(
|
||||
reference_document="Rack",
|
||||
type_of_transaction="Both",
|
||||
dimension_name="Rack",
|
||||
apply_to_all_doctypes=1,
|
||||
fetch_from_parent="Rack",
|
||||
)
|
||||
|
||||
create_custom_field(
|
||||
"Purchase Receipt", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
|
||||
)
|
||||
|
||||
create_custom_field(
|
||||
"Delivery Note", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
|
||||
)
|
||||
|
||||
frappe.reload_doc("stock", "doctype", "purchase_receipt_item")
|
||||
frappe.reload_doc("stock", "doctype", "delivery_note_item")
|
||||
|
||||
pr_doc = make_purchase_receipt(qty=2, do_not_submit=True)
|
||||
pr_doc.rack = "Rack 1"
|
||||
pr_doc.save()
|
||||
pr_doc.submit()
|
||||
|
||||
pr_doc.load_from_db()
|
||||
|
||||
self.assertEqual(pr_doc.items[0].rack, "Rack 1")
|
||||
sle_rack = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_detail_no": pr_doc.items[0].name, "voucher_type": pr_doc.doctype},
|
||||
"rack",
|
||||
)
|
||||
|
||||
self.assertEqual(sle_rack, "Rack 1")
|
||||
|
||||
dn_doc = create_delivery_note(qty=2, do_not_submit=True)
|
||||
dn_doc.rack = "Rack 1"
|
||||
dn_doc.save()
|
||||
dn_doc.submit()
|
||||
|
||||
dn_doc.load_from_db()
|
||||
|
||||
self.assertEqual(dn_doc.items[0].rack, "Rack 1")
|
||||
sle_rack = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_detail_no": dn_doc.items[0].name, "voucher_type": dn_doc.doctype},
|
||||
"rack",
|
||||
)
|
||||
|
||||
self.assertEqual(sle_rack, "Rack 1")
|
||||
|
||||
|
||||
def prepare_test_data():
|
||||
if not frappe.db.exists("DocType", "Shelf"):
|
||||
@@ -133,6 +215,28 @@ def prepare_test_data():
|
||||
|
||||
create_warehouse("Shelf Warehouse")
|
||||
|
||||
if not frappe.db.exists("DocType", "Rack"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "DocType",
|
||||
"name": "Rack",
|
||||
"module": "Stock",
|
||||
"custom": 1,
|
||||
"naming_rule": "By fieldname",
|
||||
"autoname": "field:rack_name",
|
||||
"fields": [{"label": "Rack Name", "fieldname": "rack_name", "fieldtype": "Data"}],
|
||||
"permissions": [
|
||||
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
for rack in ["Rack 1"]:
|
||||
if not frappe.db.exists("Rack", rack):
|
||||
frappe.get_doc({"doctype": "Rack", "rack_name": rack}).insert(ignore_permissions=True)
|
||||
|
||||
create_warehouse("Rack Warehouse")
|
||||
|
||||
|
||||
def create_inventory_dimension(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -562,7 +562,7 @@ $.extend(erpnext.item, {
|
||||
let selected_attributes = {};
|
||||
me.multiple_variant_dialog.$wrapper.find('.form-column').each((i, col) => {
|
||||
if(i===0) return;
|
||||
let attribute_name = $(col).find('label').html().trim();
|
||||
let attribute_name = $(col).find('.control-label').html().trim();
|
||||
selected_attributes[attribute_name] = [];
|
||||
let checked_opts = $(col).find('.checkbox input');
|
||||
checked_opts.each((i, opt) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ def get_data():
|
||||
return {
|
||||
"heatmap": True,
|
||||
"heatmap_message": _("This is based on stock movement. See {0} for details").format(
|
||||
'<a href="#query-report/Stock Ledger">' + _("Stock Ledger") + "</a>"
|
||||
'<a href="/app/query-report/Stock Ledger">' + _("Stock Ledger") + "</a>"
|
||||
),
|
||||
"fieldname": "item_code",
|
||||
"non_standard_fieldnames": {
|
||||
|
||||
@@ -48,41 +48,31 @@
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Item",
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "UOM",
|
||||
"options": "UOM",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "UOM"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Quantity that must be bought or sold per UOM",
|
||||
"fieldname": "packing_unit",
|
||||
"fieldtype": "Int",
|
||||
"label": "Packing Unit",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Packing Unit"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Name",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.brand",
|
||||
@@ -90,36 +80,29 @@
|
||||
"fieldtype": "Read Only",
|
||||
"in_list_view": 1,
|
||||
"label": "Brand",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Item Description",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Price List",
|
||||
"options": "fa fa-tags",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "fa fa-tags"
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Price List",
|
||||
"options": "Price List",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@@ -127,49 +110,37 @@
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer",
|
||||
"options": "Customer",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.buying == 1",
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier",
|
||||
"options": "Supplier",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "buying",
|
||||
"fieldtype": "Check",
|
||||
"label": "Buying",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "selling",
|
||||
"fieldtype": "Check",
|
||||
"label": "Selling",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_details",
|
||||
"fieldtype": "Section Break",
|
||||
"options": "fa fa-tag",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "fa fa-tag"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@@ -177,15 +148,11 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_br_1",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list_rate",
|
||||
@@ -197,80 +164,61 @@
|
||||
"oldfieldname": "ref_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"fieldname": "valid_from",
|
||||
"fieldtype": "Date",
|
||||
"label": "Valid From",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Valid From"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "lead_time_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Lead Time in days",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Lead Time in days"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "valid_upto",
|
||||
"fieldtype": "Date",
|
||||
"label": "Valid Upto",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Valid Upto"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_24",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "note",
|
||||
"fieldtype": "Text",
|
||||
"label": "Note",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Note"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"in_standard_filter": 1,
|
||||
"label": "Reference"
|
||||
},
|
||||
{
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Batch"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-flag",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-08 18:12:15.395772",
|
||||
"modified": "2022-09-02 16:33:55.612992",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Price",
|
||||
@@ -307,6 +255,7 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"title_field": "item_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user