mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-02 19:59:12 +00:00
Merge pull request #35901 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
13
.github/workflows/patch.yml
vendored
13
.github/workflows/patch.yml
vendored
@@ -43,9 +43,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: "gabrielfalcao/pyenv-action@v9"
|
uses: "actions/setup-python@v4"
|
||||||
with:
|
with:
|
||||||
versions: 3.10:latest, 3.7:latest
|
python-version: |
|
||||||
|
3.7
|
||||||
|
3.10
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
@@ -92,7 +94,6 @@ jobs:
|
|||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
pip install frappe-bench
|
pip install frappe-bench
|
||||||
pyenv global $(pyenv versions | grep '3.10')
|
|
||||||
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
env:
|
env:
|
||||||
DB: mariadb
|
DB: mariadb
|
||||||
@@ -107,7 +108,6 @@ jobs:
|
|||||||
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
|
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
|
||||||
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
|
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
|
||||||
|
|
||||||
pyenv global $(pyenv versions | grep '3.7')
|
|
||||||
for version in $(seq 12 13)
|
for version in $(seq 12 13)
|
||||||
do
|
do
|
||||||
echo "Updating to v$version"
|
echo "Updating to v$version"
|
||||||
@@ -120,7 +120,7 @@ jobs:
|
|||||||
git -C "apps/erpnext" checkout -q -f $branch_name
|
git -C "apps/erpnext" checkout -q -f $branch_name
|
||||||
|
|
||||||
rm -rf ~/frappe-bench/env
|
rm -rf ~/frappe-bench/env
|
||||||
bench setup env
|
bench setup env --python python3.7
|
||||||
bench pip install -e ./apps/payments
|
bench pip install -e ./apps/payments
|
||||||
bench pip install -e ./apps/erpnext
|
bench pip install -e ./apps/erpnext
|
||||||
|
|
||||||
@@ -132,9 +132,8 @@ jobs:
|
|||||||
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||||
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
||||||
|
|
||||||
pyenv global $(pyenv versions | grep '3.10')
|
|
||||||
rm -rf ~/frappe-bench/env
|
rm -rf ~/frappe-bench/env
|
||||||
bench -v setup env
|
bench -v setup env --python python3.10
|
||||||
bench pip install -e ./apps/payments
|
bench pip install -e ./apps/payments
|
||||||
bench pip install -e ./apps/erpnext
|
bench pip install -e ./apps/erpnext
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,10 @@
|
|||||||
"acc_frozen_upto",
|
"acc_frozen_upto",
|
||||||
"column_break_25",
|
"column_break_25",
|
||||||
"frozen_accounts_modifier",
|
"frozen_accounts_modifier",
|
||||||
"report_settings_sb"
|
"report_settings_sb",
|
||||||
|
"banking_tab",
|
||||||
|
"enable_party_matching",
|
||||||
|
"enable_fuzzy_matching"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -385,6 +388,26 @@
|
|||||||
"fieldname": "show_taxes_as_table_in_print",
|
"fieldname": "show_taxes_as_table_in_print",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Taxes as Table in Print"
|
"label": "Show Taxes as Table in Print"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "banking_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Banking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Auto match and set the Party in Bank Transactions",
|
||||||
|
"fieldname": "enable_party_matching",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Automatic Party Matching"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "enable_party_matching",
|
||||||
|
"description": "Approximately match the description/party name against parties",
|
||||||
|
"fieldname": "enable_fuzzy_matching",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Fuzzy Matching"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -392,7 +415,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-13 18:47:46.430291",
|
"modified": "2023-06-15 18:47:46.430291",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
178
erpnext/accounts/doctype/bank_transaction/auto_match_party.py
Normal file
178
erpnext/accounts/doctype/bank_transaction/auto_match_party.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
from typing import Tuple, Union
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import flt
|
||||||
|
from rapidfuzz import fuzz, process
|
||||||
|
|
||||||
|
|
||||||
|
class AutoMatchParty:
|
||||||
|
"""
|
||||||
|
Matches by Account/IBAN and then by Party Name/Description sequentially.
|
||||||
|
Returns when a result is obtained.
|
||||||
|
|
||||||
|
Result (if present) is of the form: (Party Type, Party,)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
return self.__dict__.get(key, None)
|
||||||
|
|
||||||
|
def match(self) -> Union[Tuple, None]:
|
||||||
|
result = None
|
||||||
|
result = AutoMatchbyAccountIBAN(
|
||||||
|
bank_party_account_number=self.bank_party_account_number,
|
||||||
|
bank_party_iban=self.bank_party_iban,
|
||||||
|
deposit=self.deposit,
|
||||||
|
).match()
|
||||||
|
|
||||||
|
fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching")
|
||||||
|
if not result and fuzzy_matching_enabled:
|
||||||
|
result = AutoMatchbyPartyNameDescription(
|
||||||
|
bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit
|
||||||
|
).match()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class AutoMatchbyAccountIBAN:
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
return self.__dict__.get(key, None)
|
||||||
|
|
||||||
|
def match(self):
|
||||||
|
if not (self.bank_party_account_number or self.bank_party_iban):
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = self.match_account_in_party()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def match_account_in_party(self) -> Union[Tuple, None]:
|
||||||
|
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
|
||||||
|
result = None
|
||||||
|
parties = get_parties_in_order(self.deposit)
|
||||||
|
or_filters = self.get_or_filters()
|
||||||
|
|
||||||
|
for party in parties:
|
||||||
|
party_result = frappe.db.get_all(
|
||||||
|
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if party == "Employee" and not party_result:
|
||||||
|
# Search in Bank Accounts first for Employee, and then Employee record
|
||||||
|
if "bank_account_no" in or_filters:
|
||||||
|
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
|
||||||
|
|
||||||
|
party_result = frappe.db.get_all(
|
||||||
|
party, or_filters=or_filters, pluck="name", limit_page_length=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if party_result:
|
||||||
|
result = (
|
||||||
|
party,
|
||||||
|
party_result[0],
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_or_filters(self) -> dict:
|
||||||
|
or_filters = {}
|
||||||
|
if self.bank_party_account_number:
|
||||||
|
or_filters["bank_account_no"] = self.bank_party_account_number
|
||||||
|
|
||||||
|
if self.bank_party_iban:
|
||||||
|
or_filters["iban"] = self.bank_party_iban
|
||||||
|
|
||||||
|
return or_filters
|
||||||
|
|
||||||
|
|
||||||
|
class AutoMatchbyPartyNameDescription:
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
return self.__dict__.get(key, None)
|
||||||
|
|
||||||
|
def match(self) -> Union[Tuple, None]:
|
||||||
|
# fuzzy search by customer/supplier & employee
|
||||||
|
if not (self.bank_party_name or self.description):
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = self.match_party_name_desc_in_party()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
|
||||||
|
"""Fuzzy search party name and/or description against parties in the system"""
|
||||||
|
result = None
|
||||||
|
parties = get_parties_in_order(self.deposit)
|
||||||
|
|
||||||
|
for party in parties:
|
||||||
|
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
||||||
|
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
|
||||||
|
|
||||||
|
for field in ["bank_party_name", "description"]:
|
||||||
|
if not self.get(field):
|
||||||
|
continue
|
||||||
|
|
||||||
|
result, skip = self.fuzzy_search_and_return_result(party, names, field)
|
||||||
|
if result or skip:
|
||||||
|
break
|
||||||
|
|
||||||
|
if result or skip:
|
||||||
|
# Skip If: It was hard to distinguish between close matches and so match is None
|
||||||
|
# OR if the right match was found
|
||||||
|
break
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||||
|
skip = False
|
||||||
|
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
||||||
|
party_name, skip = self.process_fuzzy_result(result)
|
||||||
|
|
||||||
|
if not party_name:
|
||||||
|
return None, skip
|
||||||
|
|
||||||
|
return (
|
||||||
|
party,
|
||||||
|
party_name,
|
||||||
|
), skip
|
||||||
|
|
||||||
|
def process_fuzzy_result(self, result: Union[list, None]):
|
||||||
|
"""
|
||||||
|
If there are multiple valid close matches return None as result may be faulty.
|
||||||
|
Return the result only if one accurate match stands out.
|
||||||
|
|
||||||
|
Returns: Result, Skip (whether or not to discontinue matching)
|
||||||
|
"""
|
||||||
|
PARTY, SCORE, CUTOFF = 0, 1, 80
|
||||||
|
|
||||||
|
if not result or not len(result):
|
||||||
|
return None, False
|
||||||
|
|
||||||
|
first_result = result[0]
|
||||||
|
if len(result) == 1:
|
||||||
|
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
|
||||||
|
|
||||||
|
second_result = result[1]
|
||||||
|
if first_result[SCORE] > CUTOFF:
|
||||||
|
# If multiple matches with the same score, return None but discontinue matching
|
||||||
|
# Matches were found but were too close to distinguish between
|
||||||
|
if first_result[SCORE] == second_result[SCORE]:
|
||||||
|
return None, True
|
||||||
|
|
||||||
|
return first_result[PARTY], True
|
||||||
|
else:
|
||||||
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
|
def get_parties_in_order(deposit: float) -> list:
|
||||||
|
parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive
|
||||||
|
if flt(deposit) > 0:
|
||||||
|
parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay
|
||||||
|
|
||||||
|
return parties
|
||||||
@@ -33,7 +33,11 @@
|
|||||||
"unallocated_amount",
|
"unallocated_amount",
|
||||||
"party_section",
|
"party_section",
|
||||||
"party_type",
|
"party_type",
|
||||||
"party"
|
"party",
|
||||||
|
"column_break_3czf",
|
||||||
|
"bank_party_name",
|
||||||
|
"bank_party_account_number",
|
||||||
|
"bank_party_iban"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -63,7 +67,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "\nPending\nSettled\nUnreconciled\nReconciled"
|
"options": "\nPending\nSettled\nUnreconciled\nReconciled\nCancelled"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "bank_account",
|
"fieldname": "bank_account",
|
||||||
@@ -202,11 +206,30 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Transaction Type",
|
"label": "Transaction Type",
|
||||||
"length": 50
|
"length": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3czf",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bank_party_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Party Name/Account Holder (Bank Statement)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bank_party_iban",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Party IBAN (Bank Statement)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bank_party_account_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Party Account No. (Bank Statement)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-05-29 18:36:50.475964",
|
"modified": "2023-06-06 13:58:12.821411",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Transaction",
|
"name": "Bank Transaction",
|
||||||
@@ -260,4 +283,4 @@
|
|||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "bank_account",
|
"title_field": "bank_account",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,9 @@ class BankTransaction(StatusUpdater):
|
|||||||
self.clear_linked_payment_entries()
|
self.clear_linked_payment_entries()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
|
||||||
|
self.auto_set_party()
|
||||||
|
|
||||||
_saving_flag = False
|
_saving_flag = False
|
||||||
|
|
||||||
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
|
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
|
||||||
@@ -146,6 +149,26 @@ class BankTransaction(StatusUpdater):
|
|||||||
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
|
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def auto_set_party(self):
|
||||||
|
from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty
|
||||||
|
|
||||||
|
if self.party_type and self.party:
|
||||||
|
return
|
||||||
|
|
||||||
|
result = AutoMatchParty(
|
||||||
|
bank_party_account_number=self.bank_party_account_number,
|
||||||
|
bank_party_iban=self.bank_party_iban,
|
||||||
|
bank_party_name=self.bank_party_name,
|
||||||
|
description=self.description,
|
||||||
|
deposit=self.deposit,
|
||||||
|
).match()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
party_type, party = result
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Bank Transaction", self.name, field={"party_type": party_type, "party": party}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_doctypes_for_bank_reconciliation():
|
def get_doctypes_for_bank_reconciliation():
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||||
|
|
||||||
|
|
||||||
|
class TestAutoMatchParty(FrappeTestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
create_bank_account()
|
||||||
|
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1)
|
||||||
|
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1)
|
||||||
|
return super().setUpClass()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0)
|
||||||
|
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0)
|
||||||
|
|
||||||
|
def test_match_by_account_number(self):
|
||||||
|
create_supplier_for_match(account_no="000000003716541159")
|
||||||
|
doc = create_bank_transaction(
|
||||||
|
withdrawal=1200,
|
||||||
|
transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b",
|
||||||
|
account_no="000000003716541159",
|
||||||
|
iban="DE02000000003716541159",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(doc.party_type, "Supplier")
|
||||||
|
self.assertEqual(doc.party, "John Doe & Co.")
|
||||||
|
|
||||||
|
def test_match_by_iban(self):
|
||||||
|
create_supplier_for_match(iban="DE02000000003716541159")
|
||||||
|
doc = create_bank_transaction(
|
||||||
|
withdrawal=1200,
|
||||||
|
transaction_id="c5455a224602afaa51592a9d9250600d",
|
||||||
|
account_no="000000003716541159",
|
||||||
|
iban="DE02000000003716541159",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(doc.party_type, "Supplier")
|
||||||
|
self.assertEqual(doc.party, "John Doe & Co.")
|
||||||
|
|
||||||
|
def test_match_by_party_name(self):
|
||||||
|
create_supplier_for_match(supplier_name="Jackson Ella W.")
|
||||||
|
doc = create_bank_transaction(
|
||||||
|
withdrawal=1200,
|
||||||
|
transaction_id="1f6f661f347ff7b1ea588665f473adb1",
|
||||||
|
party_name="Ella Jackson",
|
||||||
|
iban="DE04000000003716545346",
|
||||||
|
)
|
||||||
|
self.assertEqual(doc.party_type, "Supplier")
|
||||||
|
self.assertEqual(doc.party, "Jackson Ella W.")
|
||||||
|
|
||||||
|
def test_match_by_description(self):
|
||||||
|
create_supplier_for_match(supplier_name="Microsoft")
|
||||||
|
doc = create_bank_transaction(
|
||||||
|
description="Auftraggeber: microsoft payments Buchungstext: msft ..e3006b5hdy. ref. j375979555927627/5536",
|
||||||
|
withdrawal=1200,
|
||||||
|
transaction_id="8df880a2d09c3bed3fea358ca5168c5a",
|
||||||
|
party_name="",
|
||||||
|
)
|
||||||
|
self.assertEqual(doc.party_type, "Supplier")
|
||||||
|
self.assertEqual(doc.party, "Microsoft")
|
||||||
|
|
||||||
|
def test_skip_match_if_multiple_close_results(self):
|
||||||
|
create_supplier_for_match(supplier_name="Adithya Medical & General Stores")
|
||||||
|
create_supplier_for_match(supplier_name="Adithya Medical And General Stores")
|
||||||
|
|
||||||
|
doc = create_bank_transaction(
|
||||||
|
description="Paracetamol Consignment, SINV-0009",
|
||||||
|
withdrawal=24.85,
|
||||||
|
transaction_id="3a1da4ee2dc5a980138d56ef3460cbd9",
|
||||||
|
party_name="Adithya Medical & General",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mapping is skipped as both Supplier names have the same match score
|
||||||
|
self.assertEqual(doc.party_type, None)
|
||||||
|
self.assertEqual(doc.party, None)
|
||||||
|
|
||||||
|
|
||||||
|
def create_supplier_for_match(supplier_name="John Doe & Co.", iban=None, account_no=None):
|
||||||
|
if frappe.db.exists("Supplier", {"supplier_name": supplier_name}):
|
||||||
|
# Update related Bank Account details
|
||||||
|
if not (iban or account_no):
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
dt="Bank Account",
|
||||||
|
dn={"party": supplier_name},
|
||||||
|
field={"iban": iban, "bank_account_no": account_no},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create Supplier and Bank Account for the same
|
||||||
|
supplier = frappe.new_doc("Supplier")
|
||||||
|
supplier.supplier_name = supplier_name
|
||||||
|
supplier.supplier_group = "Services"
|
||||||
|
supplier.supplier_type = "Company"
|
||||||
|
supplier.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Bank", "TestBank"):
|
||||||
|
bank = frappe.new_doc("Bank")
|
||||||
|
bank.bank_name = "TestBank"
|
||||||
|
bank.insert(ignore_if_duplicate=True)
|
||||||
|
|
||||||
|
if not frappe.db.exists("Bank Account", supplier.name + " - " + "TestBank"):
|
||||||
|
bank_account = frappe.new_doc("Bank Account")
|
||||||
|
bank_account.account_name = supplier.name
|
||||||
|
bank_account.bank = "TestBank"
|
||||||
|
bank_account.iban = iban
|
||||||
|
bank_account.bank_account_no = account_no
|
||||||
|
bank_account.party_type = "Supplier"
|
||||||
|
bank_account.party = supplier.name
|
||||||
|
bank_account.insert()
|
||||||
|
|
||||||
|
|
||||||
|
def create_bank_transaction(
|
||||||
|
description=None,
|
||||||
|
withdrawal=0,
|
||||||
|
deposit=0,
|
||||||
|
transaction_id=None,
|
||||||
|
party_name=None,
|
||||||
|
account_no=None,
|
||||||
|
iban=None,
|
||||||
|
):
|
||||||
|
doc = frappe.new_doc("Bank Transaction")
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"doctype": "Bank Transaction",
|
||||||
|
"description": description or "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
||||||
|
"date": nowdate(),
|
||||||
|
"withdrawal": withdrawal,
|
||||||
|
"deposit": deposit,
|
||||||
|
"currency": "INR",
|
||||||
|
"bank_account": "Checking Account - Citi Bank",
|
||||||
|
"transaction_id": transaction_id,
|
||||||
|
"bank_party_name": party_name,
|
||||||
|
"bank_party_account_number": account_no,
|
||||||
|
"bank_party_iban": iban,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
doc.insert()
|
||||||
|
doc.submit()
|
||||||
|
doc.reload()
|
||||||
|
|
||||||
|
return doc
|
||||||
@@ -347,7 +347,10 @@ class PaymentReconciliation(Document):
|
|||||||
payment_details = self.get_payment_details(row, dr_or_cr)
|
payment_details = self.get_payment_details(row, dr_or_cr)
|
||||||
reconciled_entry.append(payment_details)
|
reconciled_entry.append(payment_details)
|
||||||
|
|
||||||
if payment_details.difference_amount:
|
if payment_details.difference_amount and row.reference_type not in [
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
]:
|
||||||
self.make_difference_entry(payment_details)
|
self.make_difference_entry(payment_details)
|
||||||
|
|
||||||
if entry_list:
|
if entry_list:
|
||||||
@@ -433,6 +436,8 @@ class PaymentReconciliation(Document):
|
|||||||
journal_entry.save()
|
journal_entry.save()
|
||||||
journal_entry.submit()
|
journal_entry.submit()
|
||||||
|
|
||||||
|
return journal_entry
|
||||||
|
|
||||||
def get_payment_details(self, row, dr_or_cr):
|
def get_payment_details(self, row, dr_or_cr):
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
{
|
{
|
||||||
@@ -598,6 +603,16 @@ class PaymentReconciliation(Document):
|
|||||||
|
|
||||||
|
|
||||||
def reconcile_dr_cr_note(dr_cr_notes, company):
|
def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||||
|
def get_difference_row(inv):
|
||||||
|
if inv.difference_amount != 0 and inv.difference_account:
|
||||||
|
difference_row = {
|
||||||
|
"account": inv.difference_account,
|
||||||
|
inv.dr_or_cr: abs(inv.difference_amount) if inv.difference_amount > 0 else 0,
|
||||||
|
reconcile_dr_or_cr: abs(inv.difference_amount) if inv.difference_amount < 0 else 0,
|
||||||
|
"cost_center": erpnext.get_default_cost_center(company),
|
||||||
|
}
|
||||||
|
return difference_row
|
||||||
|
|
||||||
for inv in dr_cr_notes:
|
for inv in dr_cr_notes:
|
||||||
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
|
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
|
||||||
|
|
||||||
@@ -642,5 +657,9 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if difference_entry := get_difference_row(inv):
|
||||||
|
jv.append("accounts", difference_entry)
|
||||||
|
|
||||||
jv.flags.ignore_mandatory = True
|
jv.flags.ignore_mandatory = True
|
||||||
jv.submit()
|
jv.submit()
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ from frappe.utils import add_days, flt, nowdate
|
|||||||
from erpnext import get_default_cost_center
|
from erpnext import get_default_cost_center
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
test_dependencies = ["Item"]
|
||||||
|
|
||||||
|
|
||||||
class TestPaymentReconciliation(FrappeTestCase):
|
class TestPaymentReconciliation(FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -163,7 +166,9 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
def create_payment_reconciliation(self):
|
def create_payment_reconciliation(self):
|
||||||
pr = frappe.new_doc("Payment Reconciliation")
|
pr = frappe.new_doc("Payment Reconciliation")
|
||||||
pr.company = self.company
|
pr.company = self.company
|
||||||
pr.party_type = "Customer"
|
pr.party_type = (
|
||||||
|
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
|
||||||
|
)
|
||||||
pr.party = self.customer
|
pr.party = self.customer
|
||||||
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
||||||
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||||
@@ -890,6 +895,42 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.assertEqual(pr.allocation[0].allocated_amount, 85)
|
self.assertEqual(pr.allocation[0].allocated_amount, 85)
|
||||||
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
||||||
|
|
||||||
|
def test_reconciliation_purchase_invoice_against_return(self):
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
|
||||||
|
).submit()
|
||||||
|
|
||||||
|
pi_return = frappe.get_doc(pi.as_dict())
|
||||||
|
pi_return.name = None
|
||||||
|
pi_return.docstatus = 0
|
||||||
|
pi_return.is_return = 1
|
||||||
|
pi_return.conversion_rate = 80
|
||||||
|
pi_return.items[0].qty = -pi_return.items[0].qty
|
||||||
|
pi_return.submit()
|
||||||
|
|
||||||
|
self.company = "_Test Company"
|
||||||
|
self.party_type = "Supplier"
|
||||||
|
self.customer = "_Test Supplier USD"
|
||||||
|
|
||||||
|
pr = self.create_payment_reconciliation()
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
invoices = []
|
||||||
|
payments = []
|
||||||
|
for invoice in pr.invoices:
|
||||||
|
if invoice.invoice_number == pi.name:
|
||||||
|
invoices.append(invoice.as_dict())
|
||||||
|
break
|
||||||
|
for payment in pr.payments:
|
||||||
|
if payment.reference_name == pi_return.name:
|
||||||
|
payments.append(payment.as_dict())
|
||||||
|
break
|
||||||
|
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
|
||||||
|
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
|
||||||
def make_customer(customer_name, currency=None):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Terms Template', {
|
frappe.ui.form.on('Payment Terms Template', {
|
||||||
setup: function(frm) {
|
refresh: function(frm) {
|
||||||
|
frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms);
|
||||||
|
},
|
||||||
|
|
||||||
|
allocate_payment_based_on_payment_terms: function(frm) {
|
||||||
|
frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from frappe.utils import flt
|
|||||||
class PaymentTermsTemplate(Document):
|
class PaymentTermsTemplate(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_invoice_portion()
|
self.validate_invoice_portion()
|
||||||
self.check_duplicate_terms()
|
self.validate_terms()
|
||||||
|
|
||||||
def validate_invoice_portion(self):
|
def validate_invoice_portion(self):
|
||||||
total_portion = 0
|
total_portion = 0
|
||||||
@@ -23,9 +23,12 @@ class PaymentTermsTemplate(Document):
|
|||||||
_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red"
|
_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red"
|
||||||
)
|
)
|
||||||
|
|
||||||
def check_duplicate_terms(self):
|
def validate_terms(self):
|
||||||
terms = []
|
terms = []
|
||||||
for term in self.terms:
|
for term in self.terms:
|
||||||
|
if self.allocate_payment_based_on_payment_terms and not term.payment_term:
|
||||||
|
frappe.throw(_("Row {0}: Payment Term is mandatory").format(term.idx))
|
||||||
|
|
||||||
term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
|
term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
|
||||||
if term_info in terms:
|
if term_info in terms:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
|
|||||||
@@ -123,22 +123,29 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
row.expected_amount = row.opening_amount;
|
row.expected_amount = row.opening_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pos_inv_promises = frm.doc.pos_transactions.map(
|
await Promise.all([
|
||||||
row => frappe.db.get_doc("POS Invoice", row.pos_invoice)
|
frappe.call({
|
||||||
);
|
method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices',
|
||||||
|
args: {
|
||||||
const pos_invoices = await Promise.all(pos_inv_promises);
|
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||||
|
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||||
for (let doc of pos_invoices) {
|
pos_profile: frm.doc.pos_profile,
|
||||||
frm.doc.grand_total += flt(doc.grand_total);
|
user: frm.doc.user
|
||||||
frm.doc.net_total += flt(doc.net_total);
|
},
|
||||||
frm.doc.total_quantity += flt(doc.total_qty);
|
callback: (r) => {
|
||||||
refresh_payments(doc, frm);
|
let pos_invoices = r.message;
|
||||||
refresh_taxes(doc, frm);
|
for (let doc of pos_invoices) {
|
||||||
refresh_fields(frm);
|
frm.doc.grand_total += flt(doc.grand_total);
|
||||||
set_html_data(frm);
|
frm.doc.net_total += flt(doc.net_total);
|
||||||
}
|
frm.doc.total_quantity += flt(doc.total_qty);
|
||||||
|
refresh_payments(doc, frm);
|
||||||
|
refresh_taxes(doc, frm);
|
||||||
|
refresh_fields(frm);
|
||||||
|
set_html_data(frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
])
|
||||||
frappe.dom.unfreeze();
|
frappe.dom.unfreeze();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="page-break">
|
<div class="page-break">
|
||||||
<div id="header-html" class="hidden-pdf">
|
<div id="header-html" class="hidden-pdf">
|
||||||
{% if letter_head %}
|
{% if letter_head.content %}
|
||||||
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
||||||
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -63,6 +63,20 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frm.set_value('to_date', frappe.datetime.get_today());
|
frm.set_value('to_date', frappe.datetime.get_today());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
report: function(frm){
|
||||||
|
let filters = {
|
||||||
|
'company': frm.doc.company,
|
||||||
|
}
|
||||||
|
if(frm.doc.report == 'Accounts Receivable'){
|
||||||
|
filters['account_type'] = 'Receivable';
|
||||||
|
}
|
||||||
|
frm.set_query("account", function() {
|
||||||
|
return {
|
||||||
|
filters: filters
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
customer_collection: function(frm){
|
customer_collection: function(frm){
|
||||||
frm.set_value('collection_name', '');
|
frm.set_value('collection_name', '');
|
||||||
if(frm.doc.customer_collection){
|
if(frm.doc.customer_collection){
|
||||||
|
|||||||
@@ -6,17 +6,24 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"report",
|
||||||
"section_break_11",
|
"section_break_11",
|
||||||
"from_date",
|
"from_date",
|
||||||
|
"posting_date",
|
||||||
"company",
|
"company",
|
||||||
"account",
|
"account",
|
||||||
"group_by",
|
"group_by",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
|
"territory",
|
||||||
"column_break_14",
|
"column_break_14",
|
||||||
"to_date",
|
"to_date",
|
||||||
"finance_book",
|
"finance_book",
|
||||||
"currency",
|
"currency",
|
||||||
"project",
|
"project",
|
||||||
|
"payment_terms_template",
|
||||||
|
"sales_partner",
|
||||||
|
"sales_person",
|
||||||
|
"based_on_payment_terms",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
"customer_collection",
|
"customer_collection",
|
||||||
"collection_name",
|
"collection_name",
|
||||||
@@ -65,14 +72,14 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.enable_auto_email == 0;",
|
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
|
||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "From Date",
|
"label": "From Date",
|
||||||
"mandatory_depends_on": "eval:doc.frequency == '';"
|
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.enable_auto_email == 0;",
|
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "To Date",
|
"label": "To Date",
|
||||||
@@ -85,6 +92,7 @@
|
|||||||
"options": "PSOA Cost Center"
|
"options": "PSOA Cost Center"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: (doc.report == 'General Ledger');",
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Table MultiSelect",
|
"fieldtype": "Table MultiSelect",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
@@ -102,7 +110,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_11",
|
"fieldname": "section_break_11",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "General Ledger Filters"
|
"label": "Report Filters"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_14",
|
"fieldname": "column_break_14",
|
||||||
@@ -162,12 +170,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Group by Voucher (Consolidated)",
|
"default": "Group by Voucher (Consolidated)",
|
||||||
|
"depends_on": "eval:(doc.report == 'General Ledger');",
|
||||||
"fieldname": "group_by",
|
"fieldname": "group_by",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Group By",
|
"label": "Group By",
|
||||||
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
|
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: (doc.report == 'General Ledger');",
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
@@ -295,6 +305,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval: (doc.report == 'General Ledger');",
|
||||||
"fieldname": "show_net_values_in_party_account",
|
"fieldname": "show_net_values_in_party_account",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Net Values in Party Account"
|
"label": "Show Net Values in Party Account"
|
||||||
@@ -308,10 +319,59 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_ocfq",
|
"fieldname": "column_break_ocfq",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "report",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Report",
|
||||||
|
"options": "General Ledger\nAccounts Receivable",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Today",
|
||||||
|
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Posting Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||||
|
"fieldname": "payment_terms_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Terms Template",
|
||||||
|
"options": "Payment Terms Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||||
|
"fieldname": "sales_partner",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Sales Partner",
|
||||||
|
"options": "Sales Partner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||||
|
"fieldname": "sales_person",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Sales Person",
|
||||||
|
"options": "Sales Person"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||||
|
"fieldname": "territory",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Territory",
|
||||||
|
"options": "Territory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
|
||||||
|
"fieldname": "based_on_payment_terms",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Based On Payment Terms"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-26 12:46:43.645455",
|
"modified": "2023-06-23 10:13:15.051950",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Statement Of Accounts",
|
"name": "Process Statement Of Accounts",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from frappe.www.printview import get_print_style
|
|||||||
|
|
||||||
from erpnext import get_company_currency
|
from erpnext import get_company_currency
|
||||||
from erpnext.accounts.party import get_party_account_currency
|
from erpnext.accounts.party import get_party_account_currency
|
||||||
|
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute as get_ar_soa
|
||||||
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import (
|
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import (
|
||||||
execute as get_ageing,
|
execute as get_ageing,
|
||||||
)
|
)
|
||||||
@@ -42,29 +43,10 @@ class ProcessStatementOfAccounts(Document):
|
|||||||
def get_report_pdf(doc, consolidated=True):
|
def get_report_pdf(doc, consolidated=True):
|
||||||
statement_dict = {}
|
statement_dict = {}
|
||||||
ageing = ""
|
ageing = ""
|
||||||
base_template_path = "frappe/www/printview.html"
|
|
||||||
template_path = (
|
|
||||||
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
|
||||||
)
|
|
||||||
|
|
||||||
for entry in doc.customers:
|
for entry in doc.customers:
|
||||||
if doc.include_ageing:
|
if doc.include_ageing:
|
||||||
ageing_filters = frappe._dict(
|
ageing = set_ageing(doc, entry)
|
||||||
{
|
|
||||||
"company": doc.company,
|
|
||||||
"report_date": doc.to_date,
|
|
||||||
"ageing_based_on": doc.ageing_based_on,
|
|
||||||
"range1": 30,
|
|
||||||
"range2": 60,
|
|
||||||
"range3": 90,
|
|
||||||
"range4": 120,
|
|
||||||
"customer": entry.customer,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
col1, ageing = get_ageing(ageing_filters)
|
|
||||||
|
|
||||||
if ageing:
|
|
||||||
ageing[0]["ageing_based_on"] = doc.ageing_based_on
|
|
||||||
|
|
||||||
tax_id = frappe.get_doc("Customer", entry.customer).tax_id
|
tax_id = frappe.get_doc("Customer", entry.customer).tax_id
|
||||||
presentation_currency = (
|
presentation_currency = (
|
||||||
@@ -72,59 +54,25 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
or doc.currency
|
or doc.currency
|
||||||
or get_company_currency(doc.company)
|
or get_company_currency(doc.company)
|
||||||
)
|
)
|
||||||
if doc.letter_head:
|
|
||||||
from frappe.www.printview import get_letter_head
|
|
||||||
|
|
||||||
letter_head = get_letter_head(doc, 0)
|
filters = get_common_filters(doc)
|
||||||
|
|
||||||
filters = frappe._dict(
|
if doc.report == "General Ledger":
|
||||||
{
|
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||||
"from_date": doc.from_date,
|
else:
|
||||||
"to_date": doc.to_date,
|
filters.update(get_ar_filters(doc, entry))
|
||||||
"company": doc.company,
|
|
||||||
"finance_book": doc.finance_book if doc.finance_book else None,
|
|
||||||
"account": [doc.account] if doc.account else None,
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": [entry.customer],
|
|
||||||
"party_name": [entry.customer_name] if entry.customer_name else None,
|
|
||||||
"presentation_currency": presentation_currency,
|
|
||||||
"group_by": doc.group_by,
|
|
||||||
"currency": doc.currency,
|
|
||||||
"cost_center": [cc.cost_center_name for cc in doc.cost_center],
|
|
||||||
"project": [p.project_name for p in doc.project],
|
|
||||||
"show_opening_entries": 0,
|
|
||||||
"include_default_book_entries": 0,
|
|
||||||
"tax_id": tax_id if tax_id else None,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
col, res = get_soa(filters)
|
|
||||||
|
|
||||||
for x in [0, -2, -1]:
|
if doc.report == "General Ledger":
|
||||||
res[x]["account"] = res[x]["account"].replace("'", "")
|
col, res = get_soa(filters)
|
||||||
|
for x in [0, -2, -1]:
|
||||||
|
res[x]["account"] = res[x]["account"].replace("'", "")
|
||||||
|
if len(res) == 3:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
ar_res = get_ar_soa(filters)
|
||||||
|
col, res = ar_res[0], ar_res[1]
|
||||||
|
|
||||||
if len(res) == 3:
|
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
||||||
continue
|
|
||||||
|
|
||||||
html = frappe.render_template(
|
|
||||||
template_path,
|
|
||||||
{
|
|
||||||
"filters": filters,
|
|
||||||
"data": res,
|
|
||||||
"ageing": ageing[0] if (doc.include_ageing and ageing) else None,
|
|
||||||
"letter_head": letter_head if doc.letter_head else None,
|
|
||||||
"terms_and_conditions": frappe.db.get_value(
|
|
||||||
"Terms and Conditions", doc.terms_and_conditions, "terms"
|
|
||||||
)
|
|
||||||
if doc.terms_and_conditions
|
|
||||||
else None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
html = frappe.render_template(
|
|
||||||
base_template_path,
|
|
||||||
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
|
|
||||||
)
|
|
||||||
statement_dict[entry.customer] = html
|
|
||||||
|
|
||||||
if not bool(statement_dict):
|
if not bool(statement_dict):
|
||||||
return False
|
return False
|
||||||
@@ -137,6 +85,110 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
return statement_dict
|
return statement_dict
|
||||||
|
|
||||||
|
|
||||||
|
def set_ageing(doc, entry):
|
||||||
|
ageing_filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": doc.company,
|
||||||
|
"report_date": doc.to_date,
|
||||||
|
"ageing_based_on": doc.ageing_based_on,
|
||||||
|
"range1": 30,
|
||||||
|
"range2": 60,
|
||||||
|
"range3": 90,
|
||||||
|
"range4": 120,
|
||||||
|
"customer": entry.customer,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
col1, ageing = get_ageing(ageing_filters)
|
||||||
|
|
||||||
|
if ageing:
|
||||||
|
ageing[0]["ageing_based_on"] = doc.ageing_based_on
|
||||||
|
|
||||||
|
return ageing
|
||||||
|
|
||||||
|
|
||||||
|
def get_common_filters(doc):
|
||||||
|
return frappe._dict(
|
||||||
|
{
|
||||||
|
"company": doc.company,
|
||||||
|
"finance_book": doc.finance_book if doc.finance_book else None,
|
||||||
|
"account": [doc.account] if doc.account else None,
|
||||||
|
"cost_center": [cc.cost_center_name for cc in doc.cost_center],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_gl_filters(doc, entry, tax_id, presentation_currency):
|
||||||
|
return {
|
||||||
|
"from_date": doc.from_date,
|
||||||
|
"to_date": doc.to_date,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": [entry.customer],
|
||||||
|
"party_name": [entry.customer_name] if entry.customer_name else None,
|
||||||
|
"presentation_currency": presentation_currency,
|
||||||
|
"group_by": doc.group_by,
|
||||||
|
"currency": doc.currency,
|
||||||
|
"project": [p.project_name for p in doc.project],
|
||||||
|
"show_opening_entries": 0,
|
||||||
|
"include_default_book_entries": 0,
|
||||||
|
"tax_id": tax_id if tax_id else None,
|
||||||
|
"show_net_values_in_party_account": doc.show_net_values_in_party_account,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_ar_filters(doc, entry):
|
||||||
|
return {
|
||||||
|
"report_date": doc.posting_date if doc.posting_date else None,
|
||||||
|
"customer_name": entry.customer,
|
||||||
|
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||||
|
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||||
|
"sales_person": doc.sales_person if doc.sales_person else None,
|
||||||
|
"territory": doc.territory if doc.territory else None,
|
||||||
|
"based_on_payment_terms": doc.based_on_payment_terms,
|
||||||
|
"report_name": "Accounts Receivable",
|
||||||
|
"ageing_based_on": doc.ageing_based_on,
|
||||||
|
"range1": 30,
|
||||||
|
"range2": 60,
|
||||||
|
"range3": 90,
|
||||||
|
"range4": 120,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_html(doc, filters, entry, col, res, ageing):
|
||||||
|
base_template_path = "frappe/www/printview.html"
|
||||||
|
template_path = (
|
||||||
|
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||||
|
if doc.report == "General Ledger"
|
||||||
|
else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
|
||||||
|
)
|
||||||
|
|
||||||
|
if doc.letter_head:
|
||||||
|
from frappe.www.printview import get_letter_head
|
||||||
|
|
||||||
|
letter_head = get_letter_head(doc, 0)
|
||||||
|
|
||||||
|
html = frappe.render_template(
|
||||||
|
template_path,
|
||||||
|
{
|
||||||
|
"filters": filters,
|
||||||
|
"data": res,
|
||||||
|
"report": {"report_name": doc.report, "columns": col},
|
||||||
|
"ageing": ageing[0] if (doc.include_ageing and ageing) else None,
|
||||||
|
"letter_head": letter_head if doc.letter_head else None,
|
||||||
|
"terms_and_conditions": frappe.db.get_value(
|
||||||
|
"Terms and Conditions", doc.terms_and_conditions, "terms"
|
||||||
|
)
|
||||||
|
if doc.terms_and_conditions
|
||||||
|
else None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
html = frappe.render_template(
|
||||||
|
base_template_path,
|
||||||
|
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
|
||||||
|
)
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
|
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
|
||||||
fields_dict = {
|
fields_dict = {
|
||||||
"Customer Group": "customer_group",
|
"Customer Group": "customer_group",
|
||||||
|
|||||||
@@ -0,0 +1,348 @@
|
|||||||
|
<style>
|
||||||
|
.print-format {
|
||||||
|
padding: 4mm;
|
||||||
|
font-size: 8.0pt !important;
|
||||||
|
}
|
||||||
|
.print-format td {
|
||||||
|
vertical-align:middle !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
||||||
|
<h4 class="text-center">
|
||||||
|
{% if (filters.customer_name) %}
|
||||||
|
{{ filters.customer_name }}
|
||||||
|
{% else %}
|
||||||
|
{{ filters.customer ~ filters.supplier }}
|
||||||
|
{% endif %}
|
||||||
|
</h4>
|
||||||
|
<h6 class="text-center">
|
||||||
|
{% if (filters.tax_id) %}
|
||||||
|
{{ _("Tax Id: ") }}{{ filters.tax_id }}
|
||||||
|
{% endif %}
|
||||||
|
</h6>
|
||||||
|
<h5 class="text-center">
|
||||||
|
{{ _(filters.ageing_based_on) }}
|
||||||
|
{{ _("Until") }}
|
||||||
|
{{ frappe.format(filters.report_date, 'Date') }}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
<div class="pull-left">
|
||||||
|
{% if(filters.payment_terms) %}
|
||||||
|
<strong>{{ _("Payment Terms") }}:</strong> {{ filters.payment_terms }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if(filters.credit_limit) %}
|
||||||
|
<strong>{{ _("Credit Limit") }}:</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if(filters.show_future_payments) %}
|
||||||
|
{% set balance_row = data.slice(-1).pop() %}
|
||||||
|
{% for i in report.columns %}
|
||||||
|
{% if i.fieldname == 'age' %}
|
||||||
|
{% set elem = i %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% set start = report.columns.findIndex(elem) %}
|
||||||
|
{% set range1 = report.columns[start].label %}
|
||||||
|
{% set range2 = report.columns[start+1].label %}
|
||||||
|
{% set range3 = report.columns[start+2].label %}
|
||||||
|
{% set range4 = report.columns[start+3].label %}
|
||||||
|
{% set range5 = report.columns[start+4].label %}
|
||||||
|
{% set range6 = report.columns[start+5].label %}
|
||||||
|
|
||||||
|
{% if(balance_row) %}
|
||||||
|
<table class="table table-bordered table-condensed">
|
||||||
|
<caption class="text-right">(Amount in {{ data[0]["currency"] ~ "" }})</caption>
|
||||||
|
<colgroup>
|
||||||
|
<col style="width: 30mm;">
|
||||||
|
<col style="width: 18mm;">
|
||||||
|
<col style="width: 18mm;">
|
||||||
|
<col style="width: 18mm;">
|
||||||
|
<col style="width: 18mm;">
|
||||||
|
<col style="width: 18mm;">
|
||||||
|
<col style="width: 18mm;">
|
||||||
|
<col style="width: 18mm;">
|
||||||
|
</colgroup>
|
||||||
|
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ _(" ") }}</th>
|
||||||
|
<th>{{ _(range1) }}</th>
|
||||||
|
<th>{{ _(range2) }}</th>
|
||||||
|
<th>{{ _(range3) }}</th>
|
||||||
|
<th>{{ _(range4) }}</th>
|
||||||
|
<th>{{ _(range5) }}</th>
|
||||||
|
<th>{{ _(range6) }}</th>
|
||||||
|
<th>{{ _("Total") }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ _("Total Outstanding") }}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ format_number(balance_row["age"], null, 2) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<td>{{ _("Future Payments") }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<tr class="cvs-footer">
|
||||||
|
<th class="text-left">{{ _("Cheques Required") }}</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||||
|
<th style="width: 10%">{{ _("Date") }}</th>
|
||||||
|
<th style="width: 4%">{{ _("Age (Days)") }}</th>
|
||||||
|
|
||||||
|
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||||
|
<th style="width: 14%">{{ _("Reference") }}</th>
|
||||||
|
<th style="width: 10%">{{ _("Sales Person") }}</th>
|
||||||
|
{% else %}
|
||||||
|
<th style="width: 24%">{{ _("Reference") }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<th style="width: 20%">
|
||||||
|
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||||
|
{{ _("Remarks") }}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Party") }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
|
||||||
|
<th style="width: 10%; text-align: right">
|
||||||
|
{% if report.report_name == "Accounts Receivable" %}
|
||||||
|
{{ _('Credit Note') }}
|
||||||
|
{% else %}
|
||||||
|
{{ _('Debit Note') }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
<th style="width: 10%; text-align: right">{{ _("Outstanding Amount") }}</th>
|
||||||
|
{% if(filters.show_future_payments) %}
|
||||||
|
{% if(report.report_name == "Accounts Receivable") %}
|
||||||
|
<th style="width: 12%">{{ _("Customer LPO No.") }}</th>
|
||||||
|
{% endif %}
|
||||||
|
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
|
||||||
|
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
|
||||||
|
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<th style="width: 40%">
|
||||||
|
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||||
|
{{ _("Remarks")}}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Party") }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
|
||||||
|
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
|
||||||
|
<th style="width: 15%">
|
||||||
|
{% if report.report_name == "Accounts Receivable Summary" %}
|
||||||
|
{{ _('Credit Note Amount') }}
|
||||||
|
{% else %}
|
||||||
|
{{ _('Debit Note Amount') }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for i in range(data|length) %}
|
||||||
|
<tr>
|
||||||
|
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||||
|
{% if(data[i]["party"]) %}
|
||||||
|
<td>{{ (data[i]["posting_date"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ data[i]["age"] }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
{{ data[i]["voucher_type"] }}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{{ data[i]["voucher_no"] }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||||
|
<td>{{ data[i]["sales_person"] }}</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not (filters.show_future_payments) %}
|
||||||
|
<td>
|
||||||
|
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
|
||||||
|
{{ data[i]["party"] }}
|
||||||
|
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||||
|
<br> {{ data[i]["customer_name"] }}
|
||||||
|
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
||||||
|
<br> {{ data[i]["supplier_name"] }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
{% if data[i]["remarks"] %}
|
||||||
|
{{ _("Remarks") }}:
|
||||||
|
{{ data[i]["remarks"] }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||||
|
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||||
|
|
||||||
|
{% if(filters.show_future_payments) %}
|
||||||
|
{% if(report.report_name == "Accounts Receivable") %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ data[i]["po_no"] }}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<td></td>
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
|
||||||
|
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||||
|
|
||||||
|
{% if(filters.show_future_payments) %}
|
||||||
|
{% if(report.report_name == "Accounts Receivable") %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ data[i]["po_no"] }}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if(data[i]["party"] or " ") %}
|
||||||
|
{% if not(data[i]["is_total_row"]) %}
|
||||||
|
<td>
|
||||||
|
{% if(not(filters.customer | filters.supplier)) %}
|
||||||
|
{{ data[i]["party"] }}
|
||||||
|
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||||
|
<br> {{ data[i]["customer_name"] }}
|
||||||
|
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
||||||
|
<br> {{ data[i]["supplier_name"] }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<br>{{ _("Remarks") }}:
|
||||||
|
{{ data[i]["remarks"] }}
|
||||||
|
</td>
|
||||||
|
{% else %}
|
||||||
|
<td><b>{{ _("Total") }}</b></td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
|
||||||
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
|
||||||
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
|
||||||
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
{% if ageing %}
|
||||||
|
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
|
||||||
|
{{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}}
|
||||||
|
</h4>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 25%">30 Days</th>
|
||||||
|
<th style="width: 25%">60 Days</th>
|
||||||
|
<th style="width: 25%">90 Days</th>
|
||||||
|
<th style="width: 25%">120 Days</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
|
||||||
|
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
|
||||||
|
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
|
||||||
|
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>
|
||||||
@@ -320,6 +320,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval: !doc.is_debit_note",
|
||||||
"fieldname": "is_return",
|
"fieldname": "is_return",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@@ -1959,6 +1960,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval: !doc.is_return",
|
||||||
"description": "Issue a debit note with 0 qty against an existing Sales Invoice",
|
"description": "Issue a debit note with 0 qty against an existing Sales Invoice",
|
||||||
"fieldname": "is_debit_note",
|
"fieldname": "is_debit_note",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@@ -2153,7 +2155,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-04-28 14:15:59.901154",
|
"modified": "2023-06-19 16:02:05.309332",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2017-12-25 16:50:53.878430",
|
"creation": "2017-12-25 16:50:53.878430",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -111,11 +112,12 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2019-11-17 23:24:11.395882",
|
"links": [],
|
||||||
|
"modified": "2023-04-10 22:02:20.406087",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Shareholder",
|
"name": "Shareholder",
|
||||||
"name_case": "Title Case",
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -158,6 +160,7 @@
|
|||||||
"search_fields": "folio_no",
|
"search_fields": "folio_no",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -573,7 +573,9 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
|
|||||||
"supplier": ("in", parties),
|
"supplier": ("in", parties),
|
||||||
"apply_tds": 1,
|
"apply_tds": 1,
|
||||||
"docstatus": 1,
|
"docstatus": 1,
|
||||||
|
"tax_withholding_category": ldc.tax_withholding_category,
|
||||||
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
|
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
|
||||||
|
"company": ldc.company,
|
||||||
},
|
},
|
||||||
"sum(tax_withholding_net_total)",
|
"sum(tax_withholding_net_total)",
|
||||||
)
|
)
|
||||||
@@ -603,7 +605,7 @@ def is_valid_certificate(
|
|||||||
):
|
):
|
||||||
valid = False
|
valid = False
|
||||||
|
|
||||||
available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount)
|
available_amount = flt(certificate_limit) - flt(deducted_amount)
|
||||||
|
|
||||||
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
|
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
|
||||||
valid = True
|
valid = True
|
||||||
|
|||||||
@@ -284,4 +284,4 @@
|
|||||||
{% } %}
|
{% } %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
|
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
|
||||||
@@ -92,7 +92,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
"project": d.project,
|
"project": d.project,
|
||||||
"company": d.company,
|
"company": d.company,
|
||||||
"purchase_order": d.purchase_order,
|
"purchase_order": d.purchase_order,
|
||||||
"purchase_receipt": d.purchase_receipt,
|
"purchase_receipt": purchase_receipt,
|
||||||
"expense_account": expense_account,
|
"expense_account": expense_account,
|
||||||
"stock_qty": d.stock_qty,
|
"stock_qty": d.stock_qty,
|
||||||
"stock_uom": d.stock_uom,
|
"stock_uom": d.stock_uom,
|
||||||
@@ -246,7 +246,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Purchase Receipt"),
|
"label": _("Purchase Receipt"),
|
||||||
"fieldname": "Purchase Receipt",
|
"fieldname": "purchase_receipt",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Purchase Receipt",
|
"options": "Purchase Receipt",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
|
|||||||
@@ -221,11 +221,6 @@ def get_balance_on(
|
|||||||
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
|
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
|
||||||
acc.check_permission("read")
|
acc.check_permission("read")
|
||||||
|
|
||||||
if report_type == "Profit and Loss":
|
|
||||||
# for pl accounts, get balance within a fiscal year
|
|
||||||
cond.append(
|
|
||||||
"posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" % year_start_date
|
|
||||||
)
|
|
||||||
# different filter for group and ledger - improved performance
|
# different filter for group and ledger - improved performance
|
||||||
if acc.is_group:
|
if acc.is_group:
|
||||||
cond.append(
|
cond.append(
|
||||||
|
|||||||
@@ -137,15 +137,15 @@ def make_depreciation_entry(asset_name, date=None):
|
|||||||
je.flags.ignore_permissions = True
|
je.flags.ignore_permissions = True
|
||||||
je.flags.planned_depr_entry = True
|
je.flags.planned_depr_entry = True
|
||||||
je.save()
|
je.save()
|
||||||
if not je.meta.get_workflow():
|
|
||||||
je.submit()
|
|
||||||
|
|
||||||
d.db_set("journal_entry", je.name)
|
d.db_set("journal_entry", je.name)
|
||||||
|
|
||||||
idx = cint(d.finance_book_id)
|
if not je.meta.get_workflow():
|
||||||
finance_books = asset.get("finance_books")[idx - 1]
|
je.submit()
|
||||||
finance_books.value_after_depreciation -= d.depreciation_amount
|
idx = cint(d.finance_book_id)
|
||||||
finance_books.db_update()
|
finance_books = asset.get("finance_books")[idx - 1]
|
||||||
|
finance_books.value_after_depreciation -= d.depreciation_amount
|
||||||
|
finance_books.db_update()
|
||||||
|
|
||||||
asset.db_set("depr_entry_posting_status", "Successful")
|
asset.db_set("depr_entry_posting_status", "Successful")
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
erpnext.hide_company();
|
|
||||||
this.show_general_ledger();
|
this.show_general_ledger();
|
||||||
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
||||||
this.show_stock_ledger();
|
this.show_stock_ledger();
|
||||||
@@ -105,10 +104,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
|||||||
return this.get_target_item_details();
|
return this.get_target_item_details();
|
||||||
}
|
}
|
||||||
|
|
||||||
target_asset() {
|
|
||||||
return this.get_target_asset_details();
|
|
||||||
}
|
|
||||||
|
|
||||||
item_code(doc, cdt, cdn) {
|
item_code(doc, cdt, cdn) {
|
||||||
var row = frappe.get_doc(cdt, cdn);
|
var row = frappe.get_doc(cdt, cdn);
|
||||||
if (cdt === "Asset Capitalization Stock Item") {
|
if (cdt === "Asset Capitalization Stock Item") {
|
||||||
@@ -223,26 +218,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_target_asset_details() {
|
|
||||||
var me = this;
|
|
||||||
|
|
||||||
if (me.frm.doc.target_asset) {
|
|
||||||
return me.frm.call({
|
|
||||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
|
|
||||||
child: me.frm.doc,
|
|
||||||
args: {
|
|
||||||
asset: me.frm.doc.target_asset,
|
|
||||||
company: me.frm.doc.company,
|
|
||||||
},
|
|
||||||
callback: function (r) {
|
|
||||||
if (!r.exc) {
|
|
||||||
me.frm.refresh_fields();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get_consumed_stock_item_details(row) {
|
get_consumed_stock_item_details(row) {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"entry_type",
|
"entry_type",
|
||||||
"target_item_code",
|
"target_item_code",
|
||||||
|
"target_asset",
|
||||||
"target_item_name",
|
"target_item_name",
|
||||||
"target_is_fixed_asset",
|
"target_is_fixed_asset",
|
||||||
"target_has_batch_no",
|
"target_has_batch_no",
|
||||||
"target_has_serial_no",
|
"target_has_serial_no",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"target_asset",
|
|
||||||
"target_asset_name",
|
"target_asset_name",
|
||||||
|
"target_asset_location",
|
||||||
"target_warehouse",
|
"target_warehouse",
|
||||||
"target_qty",
|
"target_qty",
|
||||||
"target_stock_uom",
|
"target_stock_uom",
|
||||||
@@ -85,14 +86,13 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
|
||||||
"fieldname": "target_asset",
|
"fieldname": "target_asset",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Target Asset",
|
"label": "Target Asset",
|
||||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Asset"
|
"options": "Asset",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||||
@@ -108,11 +108,11 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "asset.company",
|
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
|
"remember_last_selected_value": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
|
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length))",
|
||||||
"fieldname": "section_break_16",
|
"fieldname": "section_break_16",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Consumed Stock Items"
|
"label": "Consumed Stock Items"
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
"fieldname": "target_qty",
|
"fieldname": "target_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Target Qty",
|
"label": "Target Qty",
|
||||||
"read_only_depends_on": "target_is_fixed_asset"
|
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "target_item_code.stock_uom",
|
"fetch_from": "target_item_code.stock_uom",
|
||||||
@@ -227,7 +227,7 @@
|
|||||||
"depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
|
"depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
|
||||||
"fieldname": "section_break_26",
|
"fieldname": "section_break_26",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Consumed Asset Items"
|
"label": "Consumed Assets"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "asset_items",
|
"fieldname": "asset_items",
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
"options": "Finance Book"
|
"options": "Finance Book"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
|
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.service_items && doc.service_items.length))",
|
||||||
"fieldname": "service_expenses_section",
|
"fieldname": "service_expenses_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Service Expenses"
|
"label": "Service Expenses"
|
||||||
@@ -329,12 +329,20 @@
|
|||||||
"label": "Target Fixed Asset Account",
|
"label": "Target Fixed Asset Account",
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||||
|
"fieldname": "target_asset_location",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Target Asset Location",
|
||||||
|
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||||
|
"options": "Location"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-12 15:09:40.771332",
|
"modified": "2023-06-22 14:17:07.995120",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Capitalization",
|
"name": "Asset Capitalization",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import frappe
|
|||||||
|
|
||||||
# import erpnext
|
# import erpnext
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt, get_link_to_form
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -43,7 +43,6 @@ force_fields = [
|
|||||||
"target_has_batch_no",
|
"target_has_batch_no",
|
||||||
"target_stock_uom",
|
"target_stock_uom",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"target_fixed_asset_account",
|
|
||||||
"fixed_asset_account",
|
"fixed_asset_account",
|
||||||
"valuation_rate",
|
"valuation_rate",
|
||||||
]
|
]
|
||||||
@@ -54,7 +53,6 @@ class AssetCapitalization(StockController):
|
|||||||
self.validate_posting_time()
|
self.validate_posting_time()
|
||||||
self.set_missing_values(for_validate=True)
|
self.set_missing_values(for_validate=True)
|
||||||
self.validate_target_item()
|
self.validate_target_item()
|
||||||
self.validate_target_asset()
|
|
||||||
self.validate_consumed_stock_item()
|
self.validate_consumed_stock_item()
|
||||||
self.validate_consumed_asset_item()
|
self.validate_consumed_asset_item()
|
||||||
self.validate_service_item()
|
self.validate_service_item()
|
||||||
@@ -65,17 +63,18 @@ class AssetCapitalization(StockController):
|
|||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
self.validate_source_mandatory()
|
self.validate_source_mandatory()
|
||||||
|
if self.entry_type == "Capitalization":
|
||||||
|
self.create_target_asset()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_target_asset()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_target_asset()
|
self.restore_consumed_asset_items()
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
|
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
|
||||||
@@ -86,15 +85,6 @@ class AssetCapitalization(StockController):
|
|||||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||||
self.set(k, v)
|
self.set(k, v)
|
||||||
|
|
||||||
# Remove asset if item not a fixed asset
|
|
||||||
if not self.target_is_fixed_asset:
|
|
||||||
self.target_asset = None
|
|
||||||
|
|
||||||
target_asset_details = get_target_asset_details(self.target_asset, self.company)
|
|
||||||
for k, v in target_asset_details.items():
|
|
||||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
|
||||||
self.set(k, v)
|
|
||||||
|
|
||||||
for d in self.stock_items:
|
for d in self.stock_items:
|
||||||
args = self.as_dict()
|
args = self.as_dict()
|
||||||
args.update(d.as_dict())
|
args.update(d.as_dict())
|
||||||
@@ -146,9 +136,6 @@ class AssetCapitalization(StockController):
|
|||||||
|
|
||||||
if not target_item.is_stock_item:
|
if not target_item.is_stock_item:
|
||||||
self.target_warehouse = None
|
self.target_warehouse = None
|
||||||
if not target_item.is_fixed_asset:
|
|
||||||
self.target_asset = None
|
|
||||||
self.target_fixed_asset_account = None
|
|
||||||
if not target_item.has_batch_no:
|
if not target_item.has_batch_no:
|
||||||
self.target_batch_no = None
|
self.target_batch_no = None
|
||||||
if not target_item.has_serial_no:
|
if not target_item.has_serial_no:
|
||||||
@@ -159,17 +146,6 @@ class AssetCapitalization(StockController):
|
|||||||
|
|
||||||
self.validate_item(target_item)
|
self.validate_item(target_item)
|
||||||
|
|
||||||
def validate_target_asset(self):
|
|
||||||
if self.target_asset:
|
|
||||||
target_asset = self.get_asset_for_validation(self.target_asset)
|
|
||||||
|
|
||||||
if target_asset.item_code != self.target_item_code:
|
|
||||||
frappe.throw(
|
|
||||||
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.validate_asset(target_asset)
|
|
||||||
|
|
||||||
def validate_consumed_stock_item(self):
|
def validate_consumed_stock_item(self):
|
||||||
for d in self.stock_items:
|
for d in self.stock_items:
|
||||||
if d.item_code:
|
if d.item_code:
|
||||||
@@ -379,7 +355,11 @@ class AssetCapitalization(StockController):
|
|||||||
gl_entries, target_account, target_against, precision
|
gl_entries, target_account, target_against, precision
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not self.stock_items and not self.service_items and self.are_all_asset_items_non_depreciable:
|
||||||
|
return []
|
||||||
|
|
||||||
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
|
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def get_target_account(self):
|
def get_target_account(self):
|
||||||
@@ -422,11 +402,14 @@ class AssetCapitalization(StockController):
|
|||||||
def get_gl_entries_for_consumed_asset_items(
|
def get_gl_entries_for_consumed_asset_items(
|
||||||
self, gl_entries, target_account, target_against, precision
|
self, gl_entries, target_account, target_against, precision
|
||||||
):
|
):
|
||||||
|
self.are_all_asset_items_non_depreciable = True
|
||||||
|
|
||||||
# Consumed Assets
|
# Consumed Assets
|
||||||
for item in self.asset_items:
|
for item in self.asset_items:
|
||||||
asset = self.get_asset(item)
|
asset = frappe.get_doc("Asset", item.asset)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
|
self.are_all_asset_items_non_depreciable = False
|
||||||
depreciate_asset(asset, self.posting_date)
|
depreciate_asset(asset, self.posting_date)
|
||||||
asset.reload()
|
asset.reload()
|
||||||
|
|
||||||
@@ -507,30 +490,41 @@ class AssetCapitalization(StockController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_target_asset(self):
|
def create_target_asset(self):
|
||||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||||
if self.docstatus == 1 and self.entry_type == "Capitalization":
|
asset_doc = frappe.new_doc("Asset")
|
||||||
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
asset_doc.company = self.company
|
||||||
asset_doc.purchase_date = self.posting_date
|
asset_doc.item_code = self.target_item_code
|
||||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
asset_doc.is_existing_asset = 1
|
||||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
asset_doc.location = self.target_asset_location
|
||||||
asset_doc.prepare_depreciation_data()
|
asset_doc.available_for_use_date = self.posting_date
|
||||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
asset_doc.purchase_date = self.posting_date
|
||||||
asset_doc.save()
|
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||||
elif self.docstatus == 2:
|
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||||
for item in self.asset_items:
|
asset_doc.flags.ignore_validate = True
|
||||||
asset = self.get_asset(item)
|
asset_doc.insert()
|
||||||
asset.db_set("disposal_date", None)
|
|
||||||
self.set_consumed_asset_status(asset)
|
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
self.target_asset = asset_doc.name
|
||||||
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
|
||||||
reset_depreciation_schedule(asset, self.posting_date)
|
|
||||||
|
|
||||||
def get_asset(self, item):
|
self.target_fixed_asset_account = get_asset_category_account(
|
||||||
asset = frappe.get_doc("Asset", item.asset)
|
"fixed_asset_account", item=self.target_item_code, company=asset_doc.company
|
||||||
self.check_finance_books(item, asset)
|
)
|
||||||
return asset
|
|
||||||
|
frappe.msgprint(
|
||||||
|
_(
|
||||||
|
"Asset {0} has been created. Please set the depreciation details if any and submit it."
|
||||||
|
).format(get_link_to_form("Asset", asset_doc.name))
|
||||||
|
)
|
||||||
|
|
||||||
|
def restore_consumed_asset_items(self):
|
||||||
|
for item in self.asset_items:
|
||||||
|
asset = frappe.get_doc("Asset", item.asset)
|
||||||
|
asset.db_set("disposal_date", None)
|
||||||
|
self.set_consumed_asset_status(asset)
|
||||||
|
|
||||||
|
if asset.calculate_depreciation:
|
||||||
|
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||||
|
reset_depreciation_schedule(asset, self.posting_date)
|
||||||
|
|
||||||
def set_consumed_asset_status(self, asset):
|
def set_consumed_asset_status(self, asset):
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
@@ -580,33 +574,6 @@ def get_target_item_details(item_code=None, company=None):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_target_asset_details(asset=None, company=None):
|
|
||||||
out = frappe._dict()
|
|
||||||
|
|
||||||
# Get Asset Details
|
|
||||||
asset_details = frappe._dict()
|
|
||||||
if asset:
|
|
||||||
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
|
|
||||||
if not asset_details:
|
|
||||||
frappe.throw(_("Asset {0} does not exist").format(asset))
|
|
||||||
|
|
||||||
# Re-set item code from Asset
|
|
||||||
out.target_item_code = asset_details.item_code
|
|
||||||
|
|
||||||
# Set Asset Details
|
|
||||||
out.asset_name = asset_details.asset_name
|
|
||||||
|
|
||||||
if asset_details.item_code:
|
|
||||||
out.target_fixed_asset_account = get_asset_category_account(
|
|
||||||
"fixed_asset_account", item=asset_details.item_code, company=company
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
out.target_fixed_asset_account = None
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_consumed_stock_item_details(args):
|
def get_consumed_stock_item_details(args):
|
||||||
if isinstance(args, string_types):
|
if isinstance(args, string_types):
|
||||||
|
|||||||
@@ -39,13 +39,6 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
|
|
||||||
total_amount = 103000
|
total_amount = 103000
|
||||||
|
|
||||||
# Create assets
|
|
||||||
target_asset = create_asset(
|
|
||||||
asset_name="Asset Capitalization Target Asset",
|
|
||||||
submit=1,
|
|
||||||
warehouse="Stores - TCP1",
|
|
||||||
company=company,
|
|
||||||
)
|
|
||||||
consumed_asset = create_asset(
|
consumed_asset = create_asset(
|
||||||
asset_name="Asset Capitalization Consumable Asset",
|
asset_name="Asset Capitalization Consumable Asset",
|
||||||
asset_value=consumed_asset_value,
|
asset_value=consumed_asset_value,
|
||||||
@@ -57,7 +50,8 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
# Create and submit Asset Captitalization
|
# Create and submit Asset Captitalization
|
||||||
asset_capitalization = create_asset_capitalization(
|
asset_capitalization = create_asset_capitalization(
|
||||||
entry_type="Capitalization",
|
entry_type="Capitalization",
|
||||||
target_asset=target_asset.name,
|
target_item_code="Macbook Pro",
|
||||||
|
target_asset_location="Test Location",
|
||||||
stock_qty=stock_qty,
|
stock_qty=stock_qty,
|
||||||
stock_rate=stock_rate,
|
stock_rate=stock_rate,
|
||||||
consumed_asset=consumed_asset.name,
|
consumed_asset=consumed_asset.name,
|
||||||
@@ -86,7 +80,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||||
|
|
||||||
# Test Target Asset values
|
# Test Target Asset values
|
||||||
target_asset.reload()
|
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||||
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||||
|
|
||||||
@@ -134,13 +128,6 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
|
|
||||||
total_amount = 103000
|
total_amount = 103000
|
||||||
|
|
||||||
# Create assets
|
|
||||||
target_asset = create_asset(
|
|
||||||
asset_name="Asset Capitalization Target Asset",
|
|
||||||
submit=1,
|
|
||||||
warehouse="Stores - _TC",
|
|
||||||
company=company,
|
|
||||||
)
|
|
||||||
consumed_asset = create_asset(
|
consumed_asset = create_asset(
|
||||||
asset_name="Asset Capitalization Consumable Asset",
|
asset_name="Asset Capitalization Consumable Asset",
|
||||||
asset_value=consumed_asset_value,
|
asset_value=consumed_asset_value,
|
||||||
@@ -152,7 +139,8 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
# Create and submit Asset Captitalization
|
# Create and submit Asset Captitalization
|
||||||
asset_capitalization = create_asset_capitalization(
|
asset_capitalization = create_asset_capitalization(
|
||||||
entry_type="Capitalization",
|
entry_type="Capitalization",
|
||||||
target_asset=target_asset.name,
|
target_item_code="Macbook Pro",
|
||||||
|
target_asset_location="Test Location",
|
||||||
stock_qty=stock_qty,
|
stock_qty=stock_qty,
|
||||||
stock_rate=stock_rate,
|
stock_rate=stock_rate,
|
||||||
consumed_asset=consumed_asset.name,
|
consumed_asset=consumed_asset.name,
|
||||||
@@ -181,7 +169,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||||
|
|
||||||
# Test Target Asset values
|
# Test Target Asset values
|
||||||
target_asset.reload()
|
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||||
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||||
|
|
||||||
@@ -343,6 +331,7 @@ def create_asset_capitalization(**args):
|
|||||||
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
|
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
|
||||||
"target_item_code": target_item_code,
|
"target_item_code": target_item_code,
|
||||||
"target_asset": target_asset.name,
|
"target_asset": target_asset.name,
|
||||||
|
"target_asset_location": "Test Location",
|
||||||
"target_warehouse": target_warehouse,
|
"target_warehouse": target_warehouse,
|
||||||
"target_qty": flt(args.target_qty) or 1,
|
"target_qty": flt(args.target_qty) or 1,
|
||||||
"target_batch_no": args.target_batch_no,
|
"target_batch_no": args.target_batch_no,
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
source_name: this.frm.doc.supplier,
|
source_name: this.frm.doc.supplier,
|
||||||
target: this.frm,
|
target: this.frm,
|
||||||
setters: {
|
setters: {
|
||||||
company: me.frm.doc.company
|
company: this.frm.doc.company
|
||||||
},
|
},
|
||||||
get_query_filters: {
|
get_query_filters: {
|
||||||
docstatus: ["!=", 2],
|
docstatus: ["!=", 2],
|
||||||
|
|||||||
@@ -457,7 +457,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-11-09 18:02:59.075203",
|
"modified": "2023-05-09 15:34:13.408932",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier",
|
"name": "Supplier",
|
||||||
|
|||||||
@@ -709,6 +709,7 @@ class BuyingController(SubcontractingController):
|
|||||||
"asset_quantity": row.qty if is_grouped_asset else 0,
|
"asset_quantity": row.qty if is_grouped_asset else 0,
|
||||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||||
|
"cost_center": row.cost_center,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
|
if (frm.doc.mr_items && frm.doc.mr_items.length && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
|
||||||
frm.add_custom_button(__("Material Request"), ()=> {
|
frm.add_custom_button(__("Material Request"), ()=> {
|
||||||
frm.trigger("make_material_request");
|
frm.trigger("make_material_request");
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
|
|||||||
@@ -515,6 +515,9 @@ class ProductionPlan(Document):
|
|||||||
self.show_list_created_message("Work Order", wo_list)
|
self.show_list_created_message("Work Order", wo_list)
|
||||||
self.show_list_created_message("Purchase Order", po_list)
|
self.show_list_created_message("Purchase Order", po_list)
|
||||||
|
|
||||||
|
if not wo_list:
|
||||||
|
frappe.msgprint(_("No Work Orders were created"))
|
||||||
|
|
||||||
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
|
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
|
||||||
items_data = self.get_production_items()
|
items_data = self.get_production_items()
|
||||||
|
|
||||||
@@ -618,6 +621,9 @@ class ProductionPlan(Document):
|
|||||||
def create_work_order(self, item):
|
def create_work_order(self, item):
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
|
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
|
||||||
|
|
||||||
|
if item.get("qty") <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
wo = frappe.new_doc("Work Order")
|
wo = frappe.new_doc("Work Order")
|
||||||
wo.update(item)
|
wo.update(item)
|
||||||
wo.planned_start_date = item.get("planned_start_date") or item.get("schedule_date")
|
wo.planned_start_date = item.get("planned_start_date") or item.get("schedule_date")
|
||||||
|
|||||||
@@ -76,6 +76,13 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
|
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pln.make_work_order()
|
||||||
|
nwork_orders = frappe.get_all(
|
||||||
|
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(len(work_orders), len(nwork_orders))
|
||||||
|
|
||||||
self.assertTrue(len(work_orders), len(pln.po_items))
|
self.assertTrue(len(work_orders), len(pln.po_items))
|
||||||
|
|
||||||
for name in material_requests:
|
for name in material_requests:
|
||||||
|
|||||||
@@ -334,3 +334,4 @@ erpnext.patches.v14_0.update_company_in_ldc
|
|||||||
erpnext.patches.v14_0.set_packed_qty_in_draft_delivery_notes
|
erpnext.patches.v14_0.set_packed_qty_in_draft_delivery_notes
|
||||||
erpnext.patches.v14_0.cleanup_workspaces
|
erpnext.patches.v14_0.cleanup_workspaces
|
||||||
erpnext.patches.v14_0.enable_allow_existing_serial_no
|
erpnext.patches.v14_0.enable_allow_existing_serial_no
|
||||||
|
erpnext.patches.v14_0.set_report_in_process_SOA
|
||||||
|
|||||||
10
erpnext/patches/v14_0/set_report_in_process_SOA.py
Normal file
10
erpnext/patches/v14_0/set_report_in_process_SOA.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
process_soa = frappe.qb.DocType("Process Statement Of Accounts")
|
||||||
|
q = frappe.qb.update(process_soa).set(process_soa.report, "General Ledger")
|
||||||
|
q.run()
|
||||||
@@ -568,7 +568,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-11-08 15:52:34.462657",
|
"modified": "2023-05-09 15:38:40.255193",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
|
|||||||
@@ -78,7 +78,9 @@
|
|||||||
"salary_mode",
|
"salary_mode",
|
||||||
"bank_details_section",
|
"bank_details_section",
|
||||||
"bank_name",
|
"bank_name",
|
||||||
|
"column_break_heye",
|
||||||
"bank_ac_no",
|
"bank_ac_no",
|
||||||
|
"iban",
|
||||||
"personal_details",
|
"personal_details",
|
||||||
"marital_status",
|
"marital_status",
|
||||||
"family_background",
|
"family_background",
|
||||||
@@ -804,17 +806,26 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_104",
|
"fieldname": "column_break_104",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_heye",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.salary_mode == 'Bank'",
|
||||||
|
"fieldname": "iban",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "IBAN"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-13 10:27:14.579197",
|
"modified": "2023-03-30 15:57:05.174592",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
"name_case": "Title Case",
|
|
||||||
"naming_rule": "By \"Naming Series\" field",
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
|||||||
@@ -66,8 +66,7 @@
|
|||||||
"fieldname": "driver",
|
"fieldname": "driver",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Driver",
|
"label": "Driver",
|
||||||
"options": "Driver",
|
"options": "Driver"
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "driver.full_name",
|
"fetch_from": "driver.full_name",
|
||||||
@@ -189,10 +188,11 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-30 21:21:36.610142",
|
"modified": "2023-06-27 11:22:27.927637",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Trip",
|
"name": "Delivery Trip",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -228,5 +228,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "driver_name"
|
"title_field": "driver_name"
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,9 @@ class DeliveryTrip(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
if self._action == "submit" and not self.driver:
|
||||||
|
frappe.throw(_("A driver must be set to submit."))
|
||||||
|
|
||||||
self.validate_stop_addresses()
|
self.validate_stop_addresses()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
|
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
|
||||||
|
|
||||||
def test_make_purchase_invoice(self):
|
def test_make_purchase_invoice(self):
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_term
|
||||||
|
|
||||||
|
create_payment_term("_Test Payment Term 1 for Purchase Invoice")
|
||||||
|
create_payment_term("_Test Payment Term 2 for Purchase Invoice")
|
||||||
|
|
||||||
if not frappe.db.exists(
|
if not frappe.db.exists(
|
||||||
"Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
|
"Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
|
||||||
):
|
):
|
||||||
@@ -74,12 +79,14 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
"terms": [
|
"terms": [
|
||||||
{
|
{
|
||||||
"doctype": "Payment Terms Template Detail",
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"payment_term": "_Test Payment Term 1 for Purchase Invoice",
|
||||||
"invoice_portion": 50.00,
|
"invoice_portion": 50.00,
|
||||||
"credit_days_based_on": "Day(s) after invoice date",
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
"credit_days": 00,
|
"credit_days": 00,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Payment Terms Template Detail",
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"payment_term": "_Test Payment Term 2 for Purchase Invoice",
|
||||||
"invoice_portion": 50.00,
|
"invoice_portion": 50.00,
|
||||||
"credit_days_based_on": "Day(s) after invoice date",
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
"credit_days": 30,
|
"credit_days": 30,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from frappe.utils.user import get_users_with_role
|
|||||||
from rq.timeouts import JobTimeoutException
|
from rq.timeouts import JobTimeoutException
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
|
from erpnext.accounts.general_ledger import validate_accounting_period
|
||||||
from erpnext.accounts.utils import get_future_stock_vouchers, repost_gle_for_stock_vouchers
|
from erpnext.accounts.utils import get_future_stock_vouchers, repost_gle_for_stock_vouchers
|
||||||
from erpnext.stock.stock_ledger import (
|
from erpnext.stock.stock_ledger import (
|
||||||
get_affected_transactions,
|
get_affected_transactions,
|
||||||
@@ -43,11 +44,49 @@ class RepostItemValuation(Document):
|
|||||||
self.validate_accounts_freeze()
|
self.validate_accounts_freeze()
|
||||||
|
|
||||||
def validate_period_closing_voucher(self):
|
def validate_period_closing_voucher(self):
|
||||||
|
# Period Closing Voucher
|
||||||
year_end_date = self.get_max_year_end_date(self.company)
|
year_end_date = self.get_max_year_end_date(self.company)
|
||||||
if year_end_date and getdate(self.posting_date) <= getdate(year_end_date):
|
if year_end_date and getdate(self.posting_date) <= getdate(year_end_date):
|
||||||
msg = f"Due to period closing, you cannot repost item valuation before {year_end_date}"
|
date = frappe.format(year_end_date, "Date")
|
||||||
|
msg = f"Due to period closing, you cannot repost item valuation before {date}"
|
||||||
frappe.throw(_(msg))
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
|
# Accounting Period
|
||||||
|
if self.voucher_type:
|
||||||
|
validate_accounting_period(
|
||||||
|
[
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"company": self.company,
|
||||||
|
"voucher_type": self.voucher_type,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Closing Stock Balance
|
||||||
|
closing_stock = self.get_closing_stock_balance()
|
||||||
|
if closing_stock and closing_stock[0].name:
|
||||||
|
name = get_link_to_form("Closing Stock Balance", closing_stock[0].name)
|
||||||
|
to_date = frappe.format(closing_stock[0].to_date, "Date")
|
||||||
|
msg = f"Due to closing stock balance {name}, you cannot repost item valuation before {to_date}"
|
||||||
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
|
def get_closing_stock_balance(self):
|
||||||
|
filters = {
|
||||||
|
"company": self.company,
|
||||||
|
"status": "Completed",
|
||||||
|
"docstatus": 1,
|
||||||
|
"to_date": (">=", self.posting_date),
|
||||||
|
}
|
||||||
|
|
||||||
|
for field in ["warehouse", "item_code"]:
|
||||||
|
if self.get(field):
|
||||||
|
filters.update({field: ("in", ["", self.get(field)])})
|
||||||
|
|
||||||
|
return frappe.get_all("Closing Stock Balance", fields=["name", "to_date"], filters=filters)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_max_year_end_date(company):
|
def get_max_year_end_date(company):
|
||||||
data = frappe.get_all(
|
data = frappe.get_all(
|
||||||
|
|||||||
@@ -392,3 +392,33 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
|
|||||||
pr.cancel()
|
pr.cancel()
|
||||||
self.assertTrue(pr.docstatus == 2)
|
self.assertTrue(pr.docstatus == 2)
|
||||||
self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))
|
self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))
|
||||||
|
|
||||||
|
def test_repost_item_valuation_for_closing_stock_balance(self):
|
||||||
|
from erpnext.stock.doctype.closing_stock_balance.closing_stock_balance import (
|
||||||
|
prepare_closing_stock_balance,
|
||||||
|
)
|
||||||
|
|
||||||
|
doc = frappe.new_doc("Closing Stock Balance")
|
||||||
|
doc.company = "_Test Company"
|
||||||
|
doc.from_date = today()
|
||||||
|
doc.to_date = today()
|
||||||
|
doc.submit()
|
||||||
|
|
||||||
|
prepare_closing_stock_balance(doc.name)
|
||||||
|
doc.load_from_db()
|
||||||
|
self.assertEqual(doc.docstatus, 1)
|
||||||
|
self.assertEqual(doc.status, "Completed")
|
||||||
|
|
||||||
|
riv = frappe.new_doc("Repost Item Valuation")
|
||||||
|
riv.update(
|
||||||
|
{
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"based_on": "Item and Warehouse",
|
||||||
|
"posting_date": today(),
|
||||||
|
"posting_time": "00:01:00",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, riv.save)
|
||||||
|
doc.cancel()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ frappe.ui.form.on("Warehouse", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("parent_warehouse", function () {
|
frm.set_query("parent_warehouse", function (doc) {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
is_group: 1,
|
is_group: 1,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ dependencies = [
|
|||||||
"python-stdnum~=1.16",
|
"python-stdnum~=1.16",
|
||||||
"Unidecode~=1.2.0",
|
"Unidecode~=1.2.0",
|
||||||
"redisearch~=2.1.0",
|
"redisearch~=2.1.0",
|
||||||
|
"rapidfuzz~=2.15.0",
|
||||||
|
|
||||||
# integration dependencies
|
# integration dependencies
|
||||||
"gocardless-pro~=1.22.0",
|
"gocardless-pro~=1.22.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user