Compare commits

..

1 Commits

Author SHA1 Message Date
Ankush Menat
565542be20 ci: disable test orchestrator v13 2022-10-12 11:02:17 +05:30
244 changed files with 2454 additions and 5834 deletions

View File

@@ -66,8 +66,7 @@ ignore =
F841,
E713,
E712,
B023,
B028
B023
max-line-length = 200

View File

@@ -3,71 +3,52 @@ from urllib.parse import urlparse
import requests
WEBSITE_REPOS = [
docs_repos = [
"frappe_docs",
"erpnext_documentation",
"erpnext_com",
"frappe_io",
]
DOCUMENTATION_DOMAINS = [
"docs.erpnext.com",
"frappeframework.com",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def is_valid_url(url: str) -> bool:
parts = urlparse(url)
return all((parts.scheme, parts.netloc, parts.path))
def is_documentation_link(word: str) -> bool:
if not word.startswith("http") or not is_valid_url(word):
return False
parsed_url = urlparse(word)
if parsed_url.netloc in DOCUMENTATION_DOMAINS:
return True
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split("/")
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in WEBSITE_REPOS:
return True
return False
def contains_documentation_link(body: str) -> bool:
return any(
is_documentation_link(word)
for line in body.splitlines()
for word in line.split()
)
def check_pull_request(number: str) -> "tuple[int, str]":
response = requests.get(f"https://api.github.com/repos/frappe/erpnext/pulls/{number}")
if not response.ok:
return 1, "Pull Request Not Found! ⚠️"
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (
not title.startswith("feat")
or not head_sha
or "no-docs" in body
or "backport" in body
):
return 0, "Skipping documentation checks... 🏃"
if contains_documentation_link(body):
return 0, "Documentation Link Found. You're Awesome! 🎉"
return 1, "Documentation Link Not Found! ⚠️"
def docs_link_exists(body):
for line in body.splitlines():
for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split('/')
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
elif parsed_url.netloc == "docs.erpnext.com":
return True
if __name__ == "__main__":
exit_code, message = check_pull_request(sys.argv[1])
print(message)
sys.exit(exit_code)
pr = sys.argv[1]
response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
if response.ok:
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (title.startswith("feat")
and head_sha
and "no-docs" not in body
and "backport" not in body
):
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")

View File

@@ -12,7 +12,7 @@ jobs:
- name: 'Setup Environment'
uses: actions/setup-python@v2
with:
python-version: '3.10'
python-version: 3.6
- name: 'Clone repo'
uses: actions/checkout@v2

View File

@@ -13,10 +13,10 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
- name: Setup Node.js v14
uses: actions/setup-node@v2
with:
node-version: 18
node-version: 14
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
@@ -28,4 +28,4 @@ jobs:
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release
run: npx semantic-release

View File

@@ -16,8 +16,8 @@ repos:
- id: check-merge-conflict
- id: check-ast
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: [
@@ -32,8 +32,8 @@ repos:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
- repo: https://github.com/timothycrosley/isort
rev: 5.9.1
hooks:
- id: isort
exclude: ".*setup.py$"

View File

@@ -4,7 +4,7 @@
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @anandbaburajan @deepeshgarg007
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/erpnext_integrations/ @nextchamp-saqib
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = "13.49.2"
__version__ = "13.36.1"
def get_default_company(user=None):

View File

@@ -378,7 +378,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
return
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
if via_journal_entry:

View File

@@ -1,38 +1,38 @@
{
"country_code": "de",
"name": "SKR03 mit Kontonummern",
"tree": {
"Aktiva": {
"is_group": 1,
"country_code": "de",
"name": "SKR03 mit Kontonummern",
"tree": {
"Aktiva": {
"is_group": 1,
"root_type": "Asset",
"A - Anlagevermögen": {
"is_group": 1,
"EDV-Software": {
"account_number": "0027",
"account_type": "Fixed Asset"
},
"Geschäftsausstattung": {
"account_number": "0410",
"account_type": "Fixed Asset"
},
"Büroeinrichtung": {
"account_number": "0420",
"account_type": "Fixed Asset"
},
"Darlehen": {
"account_number": "0565"
},
"Maschinen": {
"account_number": "0210",
"account_type": "Fixed Asset"
},
"Betriebsausstattung": {
"account_number": "0400",
"account_type": "Fixed Asset"
},
"Ladeneinrichtung": {
"account_number": "0430",
"account_type": "Fixed Asset"
"A - Anlagevermögen": {
"is_group": 1,
"EDV-Software": {
"account_number": "0027",
"account_type": "Fixed Asset"
},
"Gesch\u00e4ftsausstattung": {
"account_number": "0410",
"account_type": "Fixed Asset"
},
"B\u00fcroeinrichtung": {
"account_number": "0420",
"account_type": "Fixed Asset"
},
"Darlehen": {
"account_number": "0565"
},
"Maschinen": {
"account_number": "0210",
"account_type": "Fixed Asset"
},
"Betriebsausstattung": {
"account_number": "0400",
"account_type": "Fixed Asset"
},
"Ladeneinrichtung": {
"account_number": "0430",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation": {
"account_type": "Accumulated Depreciation"
@@ -60,46 +60,36 @@
"Durchlaufende Posten": {
"account_number": "1590"
},
"Verrechnungskonto Gewinnermittlung § 4 Abs. 3 EStG, nicht ergebniswirksam": {
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"account_number": "1371"
},
"Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 7 %": {
"account_number": "1571",
"account_type": "Tax",
"tax_rate": 7.0
"Abziehbare Vorsteuer 7%": {
"account_number": "1571"
},
"Abziehbare Vorsteuer 19 %": {
"account_number": "1576",
"account_type": "Tax",
"tax_rate": 19.0
"Abziehbare Vorsteuer 19%": {
"account_number": "1576"
},
"Abziehbare Vorsteuer nach § 13b UStG 19 %": {
"account_number": "1577",
"account_type": "Tax",
"tax_rate": 19.0
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
"account_number": "1577"
},
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
}
}
},
"III. Wertpapiere": {
"is_group": 1,
"Anteile an verbundenen Unternehmen (Umlaufvermögen)": {
"account_number": "1340"
},
"Anteile an herrschender oder mit Mehrheit beteiligter Gesellschaft": {
"account_number": "1344"
},
"Sonstige Wertpapiere": {
"account_number": "1348"
}
"is_group": 1
},
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1,
"Kasse": {
"is_group": 1,
"account_type": "Cash",
"is_group": 1,
"Kasse": {
"is_group": 1,
"account_number": "1000",
"account_type": "Cash"
}
@@ -121,21 +111,21 @@
"C - Rechnungsabgrenzungsposten": {
"is_group": 1,
"Aktive Rechnungsabgrenzung": {
"account_number": "0980"
"account_number": "0980"
}
},
"D - Aktive latente Steuern": {
"is_group": 1,
"Aktive latente Steuern": {
"account_number": "0983"
"account_number": "0983"
}
},
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1
}
},
"Passiva": {
"is_group": 1,
},
"Passiva": {
"is_group": 1,
"root_type": "Liability",
"A. Eigenkapital": {
"is_group": 1,
@@ -210,32 +200,26 @@
},
"Umsatzsteuer": {
"is_group": 1,
"Umsatzsteuer 7 %": {
"account_number": "1771",
"account_type": "Tax",
"tax_rate": 7.0
"account_type": "Tax",
"Umsatzsteuer 7%": {
"account_number": "1771"
},
"Umsatzsteuer 19 %": {
"account_number": "1776",
"account_type": "Tax",
"tax_rate": 19.0
"Umsatzsteuer 19%": {
"account_number": "1776"
},
"Umsatzsteuer-Vorauszahlung": {
"account_number": "1780",
"account_type": "Tax"
"account_number": "1780"
},
"Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781"
},
"Umsatzsteuer nach § 13b UStG 19 %": {
"account_number": "1787",
"account_type": "Tax",
"tax_rate": 19.0
"Umsatzsteuer \u00a7 13b UStG 19%": {
"account_number": "1787"
},
"Umsatzsteuer Vorjahr": {
"account_number": "1790"
},
"Umsatzsteuer frühere Jahre": {
"Umsatzsteuer fr\u00fchere Jahre": {
"account_number": "1791"
}
}
@@ -250,56 +234,44 @@
"E. Passive latente Steuern": {
"is_group": 1
}
},
"Erlöse u. Erträge 2/8": {
"is_group": 1,
"root_type": "Income",
"Erlöskonten 8": {
},
"Erl\u00f6se u. Ertr\u00e4ge 2/8": {
"is_group": 1,
"root_type": "Income",
"Erl\u00f6skonten 8": {
"is_group": 1,
"Erlöse": {
"account_number": "8200",
"account_type": "Income Account"
},
"Erlöse USt. 19 %": {
"account_number": "8400",
"account_type": "Income Account"
},
"Erlöse USt. 7 %": {
"account_number": "8300",
"account_type": "Income Account"
}
},
"Ertragskonten 2": {
"is_group": 1,
"sonstige Zinsen und ähnliche Erträge": {
"account_number": "2650",
"account_type": "Income Account"
},
"Außerordentliche Erträge": {
"account_number": "2500",
"account_type": "Income Account"
},
"Sonstige Erträge": {
"account_number": "2700",
"account_type": "Income Account"
}
}
},
"Aufwendungen 2/4": {
"is_group": 1,
"Erl\u00f6se": {
"account_number": "8200",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 19%": {
"account_number": "8400",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 7%": {
"account_number": "8300",
"account_type": "Income Account"
}
},
"Ertragskonten 2": {
"is_group": 1,
"sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
"account_number": "2650",
"account_type": "Income Account"
},
"Au\u00dferordentliche Ertr\u00e4ge": {
"account_number": "2500",
"account_type": "Income Account"
},
"Sonstige Ertr\u00e4ge": {
"account_number": "2700",
"account_type": "Income Account"
}
}
},
"Aufwendungen 2/4": {
"is_group": 1,
"root_type": "Expense",
"Fremdleistungen": {
"account_number": "3100",
"account_type": "Expense Account"
},
"Fremdleistungen ohne Vorsteuer": {
"account_number": "3109",
"account_type": "Expense Account"
},
"Bauleistungen eines im Inland ansässigen Unternehmers 19 % Vorsteuer und 19 % Umsatzsteuer": {
"account_number": "3120",
"account_type": "Expense Account"
},
"Wareneingang": {
"account_number": "3200"
},
@@ -326,234 +298,234 @@
"Gegenkonto 4996-4998": {
"account_number": "4999"
},
"Abschreibungen": {
"is_group": 1,
"Abschreibungen": {
"is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830",
"account_type": "Accumulated Depreciation"
"account_number": "4830",
"account_type": "Accumulated Depreciation"
},
"Abschreibungen auf Gebäude": {
"account_number": "4831",
"account_type": "Depreciation"
"account_number": "4831",
"account_type": "Depreciation"
},
"Abschreibungen auf Kfz": {
"account_number": "4832",
"account_type": "Depreciation"
"account_number": "4832",
"account_type": "Depreciation"
},
"Sofortabschreibung GWG": {
"account_number": "4855",
"account_type": "Expense Account"
"account_number": "4855",
"account_type": "Expense Account"
}
},
"Kfz-Kosten": {
"is_group": 1,
"Kfz-Steuer": {
"account_number": "4510",
"account_type": "Expense Account"
},
"Kfz-Versicherungen": {
"account_number": "4520",
"account_type": "Expense Account"
},
"laufende Kfz-Betriebskosten": {
"account_number": "4530",
"account_type": "Expense Account"
},
"Kfz-Reparaturen": {
"account_number": "4540",
"account_type": "Expense Account"
},
"Fremdfahrzeuge": {
"account_number": "4570",
"account_type": "Expense Account"
},
"sonstige Kfz-Kosten": {
"account_number": "4580",
"account_type": "Expense Account"
}
},
"Personalkosten": {
"is_group": 1,
"Gehälter": {
"account_number": "4120",
"account_type": "Expense Account"
},
"gesetzliche soziale Aufwendungen": {
"account_number": "4130",
"account_type": "Expense Account"
},
"Aufwendungen für Altersvorsorge": {
"account_number": "4165",
"account_type": "Expense Account"
},
"Vermögenswirksame Leistungen": {
"account_number": "4170",
"account_type": "Expense Account"
},
"Aushilfslöhne": {
"account_number": "4190",
"account_type": "Expense Account"
}
},
"Raumkosten": {
"is_group": 1,
"Miete und Nebenkosten": {
"account_number": "4210",
"account_type": "Expense Account"
},
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240",
"account_type": "Expense Account"
},
"Reinigung": {
"account_number": "4250",
"account_type": "Expense Account"
}
},
"Reparatur/Instandhaltung": {
"is_group": 1,
"Reparaturen und Instandhaltungen von anderen Anlagen und Betriebs- und Geschäftsausstattung": {
"account_number": "4805",
"account_type": "Expense Account"
}
},
"Versicherungsbeiträge": {
"is_group": 1,
"Versicherungen": {
"account_number": "4360",
"account_type": "Expense Account"
},
"Beiträge": {
"account_number": "4380",
"account_type": "Expense Account"
},
"sonstige Ausgaben": {
"account_number": "4390",
"account_type": "Expense Account"
},
"steuerlich abzugsfähige Verspätungszuschläge und Zwangsgelder": {
"account_number": "4396",
"account_type": "Expense Account"
}
},
"Werbe-/Reisekosten": {
"is_group": 1,
"Werbekosten": {
"account_number": "4610",
"account_type": "Expense Account"
},
"Aufmerksamkeiten": {
"account_number": "4653",
"account_type": "Expense Account"
},
"nicht abzugsfähige Betriebsausg. aus Werbe-, Repräs.- u. Reisekosten": {
"account_number": "4665",
"account_type": "Expense Account"
},
"Reisekosten Unternehmer": {
"account_number": "4670",
"account_type": "Expense Account"
}
},
"verschiedene Kosten": {
"is_group": 1,
"Porto": {
"account_number": "4910",
"account_type": "Expense Account"
},
"Telekom": {
"account_number": "4920",
"account_type": "Expense Account"
},
"Mobilfunk D2": {
"account_number": "4921",
"account_type": "Expense Account"
},
"Internet": {
"account_number": "4922",
"account_type": "Expense Account"
},
"Bürobedarf": {
"account_number": "4930",
"account_type": "Expense Account"
},
"Zeitschriften, Bücher": {
"account_number": "4940",
"account_type": "Expense Account"
},
"Fortbildungskosten": {
"account_number": "4945",
"account_type": "Expense Account"
},
"Buchführungskosten": {
"account_number": "4955",
"account_type": "Expense Account"
},
"Abschluß- u. Prüfungskosten": {
"account_number": "4957",
"account_type": "Expense Account"
},
"Nebenkosten des Geldverkehrs": {
"account_number": "4970",
"account_type": "Expense Account"
},
"Werkzeuge und Kleingeräte": {
"account_number": "4985",
"account_type": "Expense Account"
}
},
"Zinsaufwendungen": {
"is_group": 1,
"Zinsaufwendungen für kurzfristige Verbindlichkeiten": {
"account_number": "2110",
"account_type": "Expense Account"
},
"Zinsaufwendungen für KFZ Finanzierung": {
"account_number": "2121",
"account_type": "Expense Account"
}
}
},
"Anfangsbestand 9": {
"is_group": 1,
"root_type": "Equity",
"Saldenvortragskonten": {
"is_group": 1,
"Saldenvortrag Sachkonten": {
"account_number": "9000"
},
"Saldenvorträge Debitoren": {
"account_number": "9008"
},
"Saldenvorträge Kreditoren": {
"account_number": "9009"
}
}
},
"Privatkonten 1": {
"is_group": 1,
"root_type": "Equity",
"Privatentnahmen/-einlagen": {
"is_group": 1,
"Privatentnahme allgemein": {
"account_number": "1800"
},
"Privatsteuern": {
"account_number": "1810"
},
"Sonderausgaben beschränkt abzugsfähig": {
"account_number": "1820"
},
"Sonderausgaben unbeschränkt abzugsfähig": {
"account_number": "1830"
},
"Außergewöhnliche Belastungen": {
"account_number": "1850"
},
"Privateinlagen": {
"account_number": "1890"
}
}
}
}
},
"Kfz-Kosten": {
"is_group": 1,
"Kfz-Steuer": {
"account_number": "4510",
"account_type": "Expense Account"
},
"Kfz-Versicherungen": {
"account_number": "4520",
"account_type": "Expense Account"
},
"laufende Kfz-Betriebskosten": {
"account_number": "4530",
"account_type": "Expense Account"
},
"Kfz-Reparaturen": {
"account_number": "4540",
"account_type": "Expense Account"
},
"Fremdfahrzeuge": {
"account_number": "4570",
"account_type": "Expense Account"
},
"sonstige Kfz-Kosten": {
"account_number": "4580",
"account_type": "Expense Account"
}
},
"Personalkosten": {
"is_group": 1,
"Geh\u00e4lter": {
"account_number": "4120",
"account_type": "Expense Account"
},
"gesetzliche soziale Aufwendungen": {
"account_number": "4130",
"account_type": "Expense Account"
},
"Aufwendungen f\u00fcr Altersvorsorge": {
"account_number": "4165",
"account_type": "Expense Account"
},
"Verm\u00f6genswirksame Leistungen": {
"account_number": "4170",
"account_type": "Expense Account"
},
"Aushilfsl\u00f6hne": {
"account_number": "4190",
"account_type": "Expense Account"
}
},
"Raumkosten": {
"is_group": 1,
"Miete und Nebenkosten": {
"account_number": "4210",
"account_type": "Expense Account"
},
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240",
"account_type": "Expense Account"
},
"Reinigung": {
"account_number": "4250",
"account_type": "Expense Account"
}
},
"Reparatur/Instandhaltung": {
"is_group": 1,
"Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
"account_number": "4805",
"account_type": "Expense Account"
}
},
"Versicherungsbeitr\u00e4ge": {
"is_group": 1,
"Versicherungen": {
"account_number": "4360",
"account_type": "Expense Account"
},
"Beitr\u00e4ge": {
"account_number": "4380",
"account_type": "Expense Account"
},
"sonstige Ausgaben": {
"account_number": "4390",
"account_type": "Expense Account"
},
"steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
"account_number": "4396",
"account_type": "Expense Account"
}
},
"Werbe-/Reisekosten": {
"is_group": 1,
"Werbekosten": {
"account_number": "4610",
"account_type": "Expense Account"
},
"Aufmerksamkeiten": {
"account_number": "4653",
"account_type": "Expense Account"
},
"nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
"account_number": "4665",
"account_type": "Expense Account"
},
"Reisekosten Unternehmer": {
"account_number": "4670",
"account_type": "Expense Account"
}
},
"verschiedene Kosten": {
"is_group": 1,
"Porto": {
"account_number": "4910",
"account_type": "Expense Account"
},
"Telekom": {
"account_number": "4920",
"account_type": "Expense Account"
},
"Mobilfunk D2": {
"account_number": "4921",
"account_type": "Expense Account"
},
"Internet": {
"account_number": "4922",
"account_type": "Expense Account"
},
"B\u00fcrobedarf": {
"account_number": "4930",
"account_type": "Expense Account"
},
"Zeitschriften, B\u00fccher": {
"account_number": "4940",
"account_type": "Expense Account"
},
"Fortbildungskosten": {
"account_number": "4945",
"account_type": "Expense Account"
},
"Buchf\u00fchrungskosten": {
"account_number": "4955",
"account_type": "Expense Account"
},
"Abschlu\u00df- u. Pr\u00fcfungskosten": {
"account_number": "4957",
"account_type": "Expense Account"
},
"Nebenkosten des Geldverkehrs": {
"account_number": "4970",
"account_type": "Expense Account"
},
"Werkzeuge und Kleinger\u00e4te": {
"account_number": "4985",
"account_type": "Expense Account"
}
},
"Zinsaufwendungen": {
"is_group": 1,
"Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
"account_number": "2110",
"account_type": "Expense Account"
},
"Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
"account_number": "2121",
"account_type": "Expense Account"
}
}
},
"Anfangsbestand 9": {
"is_group": 1,
"root_type": "Equity",
"Saldenvortragskonten": {
"is_group": 1,
"Saldenvortrag Sachkonten": {
"account_number": "9000"
},
"Saldenvortr\u00e4ge Debitoren": {
"account_number": "9008"
},
"Saldenvortr\u00e4ge Kreditoren": {
"account_number": "9009"
}
}
},
"Privatkonten 1": {
"is_group": 1,
"root_type": "Equity",
"Privatentnahmen/-einlagen": {
"is_group": 1,
"Privatentnahme allgemein": {
"account_number": "1800"
},
"Privatsteuern": {
"account_number": "1810"
},
"Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1820"
},
"Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1830"
},
"Au\u00dfergew\u00f6hnliche Belastungen": {
"account_number": "1850"
},
"Privateinlagen": {
"account_number": "1890"
}
}
}
}
}

View File

@@ -3,6 +3,10 @@
frappe.ui.form.on('Accounting Dimension Filter', {
refresh: function(frm, cdt, cdn) {
if (frm.doc.accounting_dimension) {
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
}
let help_content =
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
<tr><td>
@@ -64,7 +68,6 @@ frappe.ui.form.on('Accounting Dimension Filter', {
frm.clear_table("dimensions");
let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension;
frm.fields_dict["dimensions"].grid.update_docfield_property("dimension_value", "label", frm.doc.accounting_dimension);
frm.refresh_field("dimensions");
frm.trigger('setup_filters');
},

View File

@@ -43,13 +43,20 @@ frappe.ui.form.on('Bank Guarantee', {
reference_docname: function(frm) {
if (frm.doc.reference_docname && frm.doc.reference_doctype) {
let fields_to_fetch = ["grand_total"];
let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier";
if (frm.doc.reference_doctype == "Sales Order") {
fields_to_fetch.push("project");
}
fields_to_fetch.push(party_field);
frappe.call({
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_voucher_details",
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_vouchar_detials",
args: {
"bank_guarantee_type": frm.doc.bg_type,
"reference_name": frm.doc.reference_docname
"column_list": fields_to_fetch,
"doctype": frm.doc.reference_doctype,
"docname": frm.doc.reference_docname
},
callback: function(r) {
if (r.message) {

View File

@@ -2,8 +2,11 @@
# For license information, please see license.txt
import json
import frappe
from frappe import _
from frappe.desk.search import sanitize_searchfield
from frappe.model.document import Document
@@ -22,18 +25,14 @@ class BankGuarantee(Document):
@frappe.whitelist()
def get_voucher_details(bank_guarantee_type: str, reference_name: str):
if not isinstance(reference_name, str):
raise TypeError("reference_name must be a string")
fields_to_fetch = ["grand_total"]
if bank_guarantee_type == "Receiving":
doctype = "Sales Order"
fields_to_fetch.append("customer")
fields_to_fetch.append("project")
else:
doctype = "Purchase Order"
fields_to_fetch.append("supplier")
return frappe.db.get_value(doctype, reference_name, fields_to_fetch, as_dict=True)
def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list)
for col in column_list:
sanitize_searchfield(col)
return frappe.db.sql(
""" select {columns} from `tab{doctype}` where name=%s""".format(
columns=", ".join(column_list), doctype=doctype
),
docname,
as_dict=1,
)[0]

View File

@@ -299,7 +299,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
dict(
account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
),
["credit_in_account_currency as credit", "debit_in_account_currency as debit"],
["credit", "debit"],
as_dict=1,
)
gl_amount, transaction_amount = (

View File

@@ -138,7 +138,7 @@ def get_paid_amount(payment_entry, currency, bank_account):
)
elif doc.payment_type == "Pay":
paid_amount_field = (
"paid_amount" if doc.paid_from_account_currency == currency else "base_paid_amount"
"paid_amount" if doc.paid_to_account_currency == currency else "base_paid_amount"
)
return frappe.db.get_value(

View File

@@ -169,6 +169,5 @@ def auto_create_fiscal_year():
def get_from_and_to_date(fiscal_year):
fields = ["year_start_date", "year_end_date"]
cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date)
fields = ["year_start_date as from_date", "year_end_date as to_date"]
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)

View File

@@ -87,7 +87,6 @@ class JournalEntry(AccountsController):
self.check_credit_limit()
self.make_gl_entries()
self.update_advance_paid()
self.update_asset_value()
self.update_expense_claim()
self.update_inter_company_jv()
self.update_invoice_discounting()
@@ -236,29 +235,6 @@ class JournalEntry(AccountsController):
for d in to_remove:
self.remove(d)
def update_asset_value(self):
if self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
):
processed_assets.append(d.reference_name)
asset = frappe.get_doc("Asset", d.reference_name)
if asset.calculate_depreciation:
continue
depr_value = d.debit or d.credit
asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
asset.set_status()
def update_inter_company_jv(self):
if (
self.voucher_type == "Inter Company Journal Entry"
@@ -317,38 +293,19 @@ class JournalEntry(AccountsController):
d.db_update()
def unlink_asset_reference(self):
if self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
):
processed_assets.append(d.reference_name)
if d.reference_type == "Asset" and d.reference_name:
asset = frappe.get_doc("Asset", d.reference_name)
for s in asset.get("schedules"):
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
if asset.calculate_depreciation:
for s in asset.get("schedules"):
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
idx = cint(s.finance_book_id) or 1
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation += s.depreciation_amount
finance_books.db_update()
idx = cint(s.finance_book_id) or 1
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation += s.depreciation_amount
finance_books.db_update()
asset.set_status()
break
else:
depr_value = d.debit or d.credit
asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
asset.set_status()
asset.set_status()
def unlink_inter_company_jv(self):
if (

View File

@@ -45,6 +45,21 @@ frappe.ui.form.on("Journal Entry Template", {
frm.trigger("clear_child");
switch(frm.doc.voucher_type){
case "Opening Entry":
frm.set_value("is_opening", "Yes");
frappe.call({
type:"GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts",
args: {
"company": frm.doc.company
},
callback: function(r) {
if(r.message) {
add_accounts(frm.doc, r.message);
}
}
});
break;
case "Bank Entry":
case "Cash Entry":
frappe.call({

View File

@@ -20,6 +20,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
frm.dashboard.reset();
frm.doc.import_in_progress = true;
}
if (data.user != frappe.session.user) return;
if (data.count == data.total) {
setTimeout((title) => {
frm.doc.import_in_progress = false;

View File

@@ -271,10 +271,10 @@ def publish(index, total, doctype):
dict(
title=_("Opening Invoice Creation In Progress"),
message=_("Creating {} out of {} {}").format(index + 1, total, doctype),
user=frappe.session.user,
count=index + 1,
total=total,
),
user=frappe.session.user,
)

View File

@@ -67,6 +67,7 @@ class PaymentEntry(AccountsController):
self.set_missing_values()
self.validate_payment_type()
self.validate_party_details()
self.validate_bank_accounts()
self.set_exchange_rate()
self.validate_mandatory()
self.validate_reference_documents()
@@ -249,6 +250,23 @@ class PaymentEntry(AccountsController):
if not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
if self.party_account and self.party_type in ("Customer", "Supplier"):
self.validate_account_type(
self.party_account, [erpnext.get_party_account_type(self.party_type)]
)
def validate_bank_accounts(self):
if self.payment_type in ("Pay", "Internal Transfer"):
self.validate_account_type(self.paid_from, ["Bank", "Cash"])
if self.payment_type in ("Receive", "Internal Transfer"):
self.validate_account_type(self.paid_to, ["Bank", "Cash"])
def validate_account_type(self, account, account_types):
account_type = frappe.db.get_value("Account", account, "account_type")
# if account_type not in account_types:
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
self.set_target_exchange_rate(ref_doc)
@@ -1255,7 +1273,6 @@ def get_outstanding_reference_documents(args):
args.get("party_type"),
args.get("party"),
args.get("party_account"),
args.get("company"),
filters=args,
condition=condition,
)

View File

@@ -25,8 +25,7 @@
"in_list_view": 1,
"label": "Type",
"options": "DocType",
"reqd": 1,
"search_index": 1
"reqd": 1
},
{
"columns": 2,
@@ -36,8 +35,7 @@
"in_list_view": 1,
"label": "Name",
"options": "reference_doctype",
"reqd": 1,
"search_index": 1
"reqd": 1
},
{
"fieldname": "due_date",
@@ -106,7 +104,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-12-12 12:31:44.919895",
"modified": "2021-09-26 17:06:55.597389",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
@@ -115,6 +113,5 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -3,10 +3,8 @@
import frappe
from frappe import _, msgprint, qb
from frappe import _, msgprint
from frappe.model.document import Document
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Sum
from frappe.utils import flt, getdate, nowdate, today
import erpnext
@@ -49,10 +47,6 @@ class PaymentReconciliation(Document):
def get_payment_entries(self):
order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order"
condition = self.get_conditions(get_payments=True)
if self.get("cost_center"):
condition += " and cost_center = '{0}' ".format(self.cost_center)
payment_entries = get_advance_payment_entries(
self.party_type,
self.party,
@@ -67,10 +61,6 @@ class PaymentReconciliation(Document):
def get_jv_entries(self):
condition = self.get_conditions()
if self.get("cost_center"):
condition += " and t2.cost_center = '{0}' ".format(self.cost_center)
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
@@ -122,79 +112,54 @@ class PaymentReconciliation(Document):
return list(journal_entries)
def get_dr_or_cr_notes(self):
gl = qb.DocType("GL Entry")
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
doc = qb.DocType(voucher_type)
# build conditions
sub_query_conditions = []
conditions = []
sub_query_conditions.append(doc.company == self.company)
if self.get("from_payment_date"):
sub_query_conditions.append(doc.posting_date.gte(self.from_payment_date))
if self.get("to_payment_date"):
sub_query_conditions.append(doc.posting_date.lte(self.to_payment_date))
if self.get("cost_center"):
sub_query_conditions.append(doc.cost_center == self.cost_center)
condition = self.get_conditions(get_return_invoices=True)
dr_or_cr = (
gl["credit_in_account_currency"]
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else gl["debit_in_account_currency"]
else "debit_in_account_currency"
)
reconciled_dr_or_cr = (
gl["debit_in_account_currency"]
if dr_or_cr.name == "credit_in_account_currency"
else gl["credit_in_account_currency"]
"debit_in_account_currency"
if dr_or_cr == "credit_in_account_currency"
else "credit_in_account_currency"
)
having_clause = qb.Field("amount") > 0
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
if self.minimum_payment_amount:
having_clause = qb.Field("amount") >= self.minimum_payment_amount
if self.maximum_payment_amount:
having_clause = having_clause & qb.Field("amount") <= self.maximum_payment_amount
sub_query = (
qb.from_(doc)
.select(doc.name)
.where(Criterion.all(sub_query_conditions))
.where(
(doc.docstatus == 1)
& (doc.is_return == 1)
& ((doc.return_against == "") | (doc.return_against.isnull()))
)
return frappe.db.sql(
""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, doc.posting_date,
account_currency as currency
FROM `tab{doc}` doc, `tabGL Entry` gl
WHERE
(doc.name = gl.against_voucher or doc.name = gl.voucher_no)
and doc.{party_type_field} = %(party)s
and doc.is_return = 1 and ifnull(doc.return_against, "") = ""
and gl.against_voucher_type = %(voucher_type)s
and doc.docstatus = 1 and gl.party = %(party)s
and gl.party_type = %(party_type)s and gl.account = %(account)s
and gl.is_cancelled = 0 {condition}
GROUP BY doc.name
Having
amount > 0
ORDER BY doc.posting_date
""".format(
doc=voucher_type,
dr_or_cr=dr_or_cr,
reconciled_dr_or_cr=reconciled_dr_or_cr,
party_type_field=frappe.scrub(self.party_type),
condition=condition or "",
),
{
"party": self.party,
"party_type": self.party_type,
"voucher_type": voucher_type,
"account": self.receivable_payable_account,
},
as_dict=1,
)
query = (
qb.from_(gl)
.select(
gl.against_voucher_type.as_("reference_type"),
gl.against_voucher.as_("reference_name"),
(Sum(dr_or_cr) - Sum(reconciled_dr_or_cr)).as_("amount"),
gl.posting_date,
gl.account_currency.as_("currency"),
)
.where(
(gl.against_voucher.isin(sub_query))
& (gl.against_voucher_type == voucher_type)
& (gl.is_cancelled == 0)
& (gl.account == self.receivable_payable_account)
& (gl.party_type == self.party_type)
& (gl.party == self.party)
)
.where(Criterion.all(conditions))
.groupby(gl.against_voucher)
.having(having_clause)
)
dr_cr_notes = query.run(as_dict=True)
return dr_cr_notes
def add_payment_entries(self, non_reconciled_payments):
self.set("payments", [])
@@ -207,11 +172,8 @@ class PaymentReconciliation(Document):
condition = self.get_conditions(get_invoices=True)
if self.get("cost_center"):
condition += " and cost_center = '{0}' ".format(self.cost_center)
non_reconciled_invoices = get_outstanding_invoices(
self.party_type, self.party, self.receivable_payable_account, self.company, condition=condition
self.party_type, self.party, self.receivable_payable_account, condition=condition
)
if self.invoice_limit:
@@ -392,9 +354,12 @@ class PaymentReconciliation(Document):
if not invoices_to_reconcile:
frappe.throw(_("No records found in Allocation table"))
def get_conditions(self, get_invoices=False, get_payments=False):
def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
condition = " and company = '{0}' ".format(self.company)
if self.get("cost_center"):
condition = " and cost_center = '{0}' ".format(self.cost_center)
if get_invoices:
condition += (
" and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date))
@@ -420,7 +385,35 @@ class PaymentReconciliation(Document):
condition += " and {dr_or_cr} <= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.maximum_invoice_amount)
)
elif get_payments:
elif get_return_invoices:
condition = " and doc.company = '{0}' ".format(self.company)
condition += (
" and doc.posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
if self.from_payment_date
else ""
)
condition += (
" and doc.posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
if self.to_payment_date
else ""
)
dr_or_cr = (
"debit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "credit_in_account_currency"
)
if self.minimum_invoice_amount:
condition += " and gl.{dr_or_cr} >= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.minimum_payment_amount)
)
if self.maximum_invoice_amount:
condition += " and gl.{dr_or_cr} <= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.maximum_payment_amount)
)
else:
condition += (
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
if self.from_payment_date

View File

@@ -25,7 +25,7 @@ frappe.ui.form.on('POS Closing Entry', {
frappe.realtime.on('closing_process_complete', async function(data) {
await frm.reload_doc();
if (frm.doc.status == 'Failed' && frm.doc.error_message) {
if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) {
frappe.msgprint({
title: __('POS Closing Failed'),
message: frm.doc.error_message,

View File

@@ -21,24 +21,8 @@ class POSClosingEntry(StatusUpdater):
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
self.validate_duplicate_pos_invoices()
self.validate_pos_invoices()
def validate_duplicate_pos_invoices(self):
pos_occurences = {}
for idx, inv in enumerate(self.pos_transactions, 1):
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
error_list = []
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
)
if error_list:
frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True)
def validate_pos_invoices(self):
invalid_rows = []
for d in self.pos_transactions:

View File

@@ -161,7 +161,7 @@ class POSInvoice(SalesInvoice):
bold_item_name = frappe.bold(item.item_name)
bold_extra_batch_qty_needed = frappe.bold(
abs(available_batch_qty - reserved_batch_qty - item.stock_qty)
abs(available_batch_qty - reserved_batch_qty - item.qty)
)
bold_invalid_batch_no = frappe.bold(item.batch_no)
@@ -172,7 +172,7 @@ class POSInvoice(SalesInvoice):
).format(item.idx, bold_invalid_batch_no, bold_item_name),
title=_("Item Unavailable"),
)
elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0:
elif (available_batch_qty - reserved_batch_qty - item.qty) < 0:
frappe.throw(
_(
"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
@@ -246,7 +246,7 @@ class POSInvoice(SalesInvoice):
),
title=_("Item Unavailable"),
)
elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
elif is_stock_item and flt(available_stock) < flt(d.qty):
frappe.throw(
_(
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -652,7 +652,7 @@ def get_bundle_availability(bundle_item_code, warehouse):
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
available_qty = item_bin_qty - item_pos_reserved_qty
max_available_bundles = available_qty / item.stock_qty
max_available_bundles = available_qty / item.qty
if bundle_bin_qty > max_available_bundles and frappe.get_value(
"Item", item.item_code, "is_stock_item"
):

View File

@@ -495,67 +495,6 @@ class TestPOSInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, pos.submit)
def test_value_error_on_serial_no_validation(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item(
company="_Test Company",
target_warehouse="Stores - _TC",
cost_center="Main - _TC",
expense_account="Cost of Goods Sold - _TC",
)
serial_nos = se.get("items")[0].serial_no
# make a pos invoice
pos = create_pos_invoice(
company="_Test Company",
debit_to="Debtors - _TC",
account_for_change_amount="Cash - _TC",
warehouse="Stores - _TC",
income_account="Sales - _TC",
expense_account="Cost of Goods Sold - _TC",
cost_center="Main - _TC",
item=se.get("items")[0].item_code,
rate=1000,
qty=1,
do_not_save=1,
)
pos.get("items")[0].has_serial_no = 1
pos.get("items")[0].serial_no = serial_nos.split("\n")[0]
pos.set("payments", [])
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
)
pos = pos.save().submit()
# make a return
pos_return = make_sales_return(pos.name)
pos_return.paid_amount = pos_return.grand_total
pos_return.save()
pos_return.submit()
# set docstatus to 2 for pos to trigger this issue
frappe.db.set_value("POS Invoice", pos.name, "docstatus", 2)
pos2 = create_pos_invoice(
company="_Test Company",
debit_to="Debtors - _TC",
account_for_change_amount="Cash - _TC",
warehouse="Stores - _TC",
income_account="Sales - _TC",
expense_account="Cost of Goods Sold - _TC",
cost_center="Main - _TC",
item=se.get("items")[0].item_code,
rate=1000,
qty=1,
do_not_save=1,
)
pos2.get("items")[0].has_serial_no = 1
pos2.get("items")[0].serial_no = serial_nos.split("\n")[0]
# Value error should not be triggered on validation
pos2.save()
def test_loyalty_points(self):
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,

View File

@@ -18,22 +18,6 @@ class POSInvoiceMergeLog(Document):
def validate(self):
self.validate_customer()
self.validate_pos_invoice_status()
self.validate_duplicate_pos_invoices()
def validate_duplicate_pos_invoices(self):
pos_occurences = {}
for idx, inv in enumerate(self.pos_invoices, 1):
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
error_list = []
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
)
if error_list:
frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True)
def validate_customer(self):
if self.merge_invoices_based_on == "Customer Group":
@@ -443,14 +427,12 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
if closing_entry:
closing_entry.set_status(update=True, status="Failed")
if type(error_message) == list:
error_message = frappe.json.dumps(error_message)
closing_entry.db_set("error_message", error_message)
raise
finally:
frappe.db.commit()
frappe.publish_realtime("closing_process_complete", user=frappe.session.user)
frappe.publish_realtime("closing_process_complete", {"user": frappe.session.user})
def cancel_merge_logs(merge_logs, closing_entry=None):
@@ -477,7 +459,7 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
finally:
frappe.db.commit()
frappe.publish_realtime("closing_process_complete", user=frappe.session.user)
frappe.publish_realtime("closing_process_complete", {"user": frappe.session.user})
def enqueue_job(job, **kwargs):

View File

@@ -269,18 +269,6 @@ def get_serial_no_for_item(args):
return item_details
def update_pricing_rule_uom(pricing_rule, args):
child_doc = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}.get(
pricing_rule.apply_on
)
apply_on_field = frappe.scrub(pricing_rule.apply_on)
for row in pricing_rule.get(child_doc):
if row.get(apply_on_field) == args.get(apply_on_field):
pricing_rule.uom = row.uom
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
@@ -337,8 +325,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if isinstance(pricing_rule, string_types):
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
update_pricing_rule_uom(pricing_rule, args)
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or []
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
if pricing_rule.get("suggestion"):
continue
@@ -452,15 +439,12 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.currency == args.currency:
pricing_rule_rate = pricing_rule.rate
# TODO https://github.com/frappe/erpnext/pull/23636 solve this in some other way.
if pricing_rule_rate:
is_blank_uom = pricing_rule.get("uom") != args.get("uom")
# Override already set price list rate (from item price)
# if pricing_rule_rate > 0
item_details.update(
{
"price_list_rate": pricing_rule_rate
* (args.get("conversion_factor", 1) if is_blank_uom else 1),
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
}
)
item_details.update({"discount_percentage": 0.0})

View File

@@ -597,255 +597,6 @@ class TestPricingRule(unittest.TestCase):
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
item.delete()
def test_item_price_with_blank_uom_pricing_rule(self):
properties = {
"item_code": "Item Blank UOM",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)
make_item_price("Item Blank UOM", "_Test Price List", 100)
pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item Blank UOM Rule",
"apply_on": "Item Code",
"items": [
{
"item_code": "Item Blank UOM",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
si = create_sales_invoice(
do_not_save=True, item_code="Item Blank UOM", uom="Box", conversion_factor=10
)
si.selling_price_list = "_Test Price List"
si.save()
# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
self.assertEqual(si.items[0].price_list_rate, 1010)
self.assertEqual(si.items[0].rate, 1010)
si.delete()
si = create_sales_invoice(do_not_save=True, item_code="Item Blank UOM", uom="Nos")
si.selling_price_list = "_Test Price List"
si.save()
# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)
si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item Blank UOM"}).delete()
item.delete()
def test_item_price_with_selling_uom_pricing_rule(self):
properties = {
"item_code": "Item UOM other than Stock",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)
make_item_price("Item UOM other than Stock", "_Test Price List", 100)
pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item UOM other than Stock Rule",
"apply_on": "Item Code",
"items": [
{
"item_code": "Item UOM other than Stock",
"uom": "Box",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
si = create_sales_invoice(
do_not_save=True, item_code="Item UOM other than Stock", uom="Box", conversion_factor=10
)
si.selling_price_list = "_Test Price List"
si.save()
# UOM is Box so apply pricing_rule only on Box UOM.
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)
si.delete()
si = create_sales_invoice(do_not_save=True, item_code="Item UOM other than Stock", uom="Nos")
si.selling_price_list = "_Test Price List"
si.save()
# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
self.assertEqual(si.items[0].price_list_rate, 100)
self.assertEqual(si.items[0].rate, 100)
si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item UOM other than Stock"}).delete()
item.delete()
def test_item_group_price_with_blank_uom_pricing_rule(self):
group = frappe.get_doc(
doctype="Item Group",
item_group_name="_Test Pricing Rule Item Group",
parent_item_group="All Item Groups",
)
group.save()
properties = {
"item_code": "Item with Group Blank UOM",
"item_group": "_Test Pricing Rule Item Group",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)
make_item_price("Item with Group Blank UOM", "_Test Price List", 100)
pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item with Group Blank UOM Rule",
"apply_on": "Item Group",
"item_groups": [
{
"item_group": "_Test Pricing Rule Item Group",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
si = create_sales_invoice(
do_not_save=True, item_code="Item with Group Blank UOM", uom="Box", conversion_factor=10
)
si.selling_price_list = "_Test Price List"
si.save()
# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
self.assertEqual(si.items[0].price_list_rate, 1010)
self.assertEqual(si.items[0].rate, 1010)
si.delete()
si = create_sales_invoice(do_not_save=True, item_code="Item with Group Blank UOM", uom="Nos")
si.selling_price_list = "_Test Price List"
si.save()
# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)
si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item with Group Blank UOM"}).delete()
item.delete()
group.delete()
def test_item_group_price_with_selling_uom_pricing_rule(self):
group = frappe.get_doc(
doctype="Item Group",
item_group_name="_Test Pricing Rule Item Group UOM",
parent_item_group="All Item Groups",
)
group.save()
properties = {
"item_code": "Item with Group UOM other than Stock",
"item_group": "_Test Pricing Rule Item Group UOM",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)
make_item_price("Item with Group UOM other than Stock", "_Test Price List", 100)
pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item with Group UOM other than Stock Rule",
"apply_on": "Item Group",
"item_groups": [
{
"item_group": "_Test Pricing Rule Item Group UOM",
"uom": "Box",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
si = create_sales_invoice(
do_not_save=True,
item_code="Item with Group UOM other than Stock",
uom="Box",
conversion_factor=10,
)
si.selling_price_list = "_Test Price List"
si.save()
# UOM is Box so apply pricing_rule only on Box UOM.
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)
si.delete()
si = create_sales_invoice(
do_not_save=True, item_code="Item with Group UOM other than Stock", uom="Nos"
)
si.selling_price_list = "_Test Price List"
si.save()
# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
self.assertEqual(si.items[0].price_list_rate, 100)
self.assertEqual(si.items[0].rate, 100)
si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item with Group UOM other than Stock"}).delete()
item.delete()
group.delete()
def test_pricing_rule_for_different_currency(self):
make_item("Test Sanitizer Item")

View File

@@ -111,12 +111,6 @@ def _get_pricing_rules(apply_on, args, values):
)
if apply_on_field == "item_code":
if args.get("uom", None):
item_conditions += (
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=args.get("uom")
)
)
if "variant_of" not in args:
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
@@ -127,12 +121,7 @@ def _get_pricing_rules(apply_on, args, values):
values["variant_of"] = args.variant_of
elif apply_on_field == "item_group":
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
if args.get("uom", None):
item_conditions += (
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=args.get("uom")
)
)
conditions += get_other_conditions(conditions, values, args)
warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`")
if warehouse_conditions:

View File

@@ -49,6 +49,7 @@
<br>
{% endif %}
{{ _("Against") }}: {{ row.against }}
<br>{{ _("Remarks") }}: {{ row.remarks }}
{% if row.bill_no %}
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}

View File

@@ -8,8 +8,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
},
refresh: function(frm){
if(!frm.doc.__islocal) {
frm.add_custom_button(__('Send Emails'), function(){
if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."))
frm.add_custom_button('Send Emails',function(){
frappe.call({
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
args: {
@@ -25,9 +24,8 @@ frappe.ui.form.on('Process Statement Of Accounts', {
}
});
});
frm.add_custom_button(__('Download'), function(){
if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."))
let url = frappe.urllib.get_full_url(
frm.add_custom_button('Download',function(){
var url = frappe.urllib.get_full_url(
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
+ 'document_name='+encodeURIComponent(frm.doc.name))
$.ajax({

View File

@@ -27,7 +27,6 @@
"customers",
"preferences",
"orientation",
"include_break",
"include_ageing",
"ageing_based_on",
"section_break_14",
@@ -285,16 +284,10 @@
"fieldtype": "Link",
"label": "Terms and Conditions",
"options": "Terms and Conditions"
},
{
"default": "1",
"fieldname": "include_break",
"fieldtype": "Check",
"label": "Page Break After Each SoA"
}
],
"links": [],
"modified": "2022-10-17 17:47:08.662475",
"modified": "2021-09-06 21:00:45.732505",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",
@@ -327,6 +320,5 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -6,7 +6,6 @@ import copy
import frappe
from frappe import _
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
from frappe.utils import add_days, add_months, format_date, getdate, today
from frappe.utils.jinja import validate_template
@@ -129,8 +128,7 @@ def get_report_pdf(doc, consolidated=True):
if not bool(statement_dict):
return False
elif consolidated:
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
result = delimiter.join(list(statement_dict.values()))
result = "".join(list(statement_dict.values()))
return get_pdf(result, {"orientation": doc.orientation})
else:
for customer, statement_html in statement_dict.items():
@@ -242,6 +240,8 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
if int(primary_mandatory):
if primary_email == "":
continue
elif (billing_email == "") and (primary_email == ""):
continue
customer_list.append(
{"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
@@ -273,12 +273,8 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
link.link_doctype='Customer'
and link.link_name=%s
and contact.is_billing_contact=1
{mcond}
ORDER BY
contact.creation desc
""".format(
mcond=get_match_cond("Contact")
),
contact.creation desc""",
customer_name,
)
@@ -317,8 +313,6 @@ def send_emails(document_name, from_scheduler=False):
attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}]
recipients, cc = get_recipients_and_cc(customer, doc)
if not recipients:
continue
context = get_context(customer, doc)
subject = frappe.render_template(doc.subject, context)
message = frappe.render_template(doc.body, context)

View File

@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
this._super();
// Ignore linked advances
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice'];
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry'];
if(!this.frm.doc.__islocal) {
// show credit_to in print format
@@ -569,10 +569,6 @@ frappe.ui.form.on("Purchase Invoice", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
if (frm.is_new()) {
frm.clear_table("tax_withheld_vouchers");
}
},
is_subcontracted: function(frm) {

View File

@@ -67,9 +67,6 @@ class PurchaseInvoice(BuyingController):
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
self.set_onload("supplier_tds", supplier_tds)
if self.is_new():
self.set("tax_withheld_vouchers", [])
def before_save(self):
if not self.on_hold:
self.release_date = ""
@@ -598,7 +595,7 @@ class PurchaseInvoice(BuyingController):
def make_supplier_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introduction of posting GLE based on rounded total
# because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = (
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
)
@@ -698,10 +695,6 @@ class PurchaseInvoice(BuyingController):
)
)
credit_amount = item.base_net_amount
if self.is_internal_supplier and item.valuation_rate:
credit_amount = flt(item.valuation_rate * item.stock_qty)
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
gl_entries.append(
self.get_gl_dict(
@@ -711,7 +704,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
},
warehouse_account[item.from_warehouse]["account_currency"],
item=item,
@@ -799,7 +792,10 @@ class PurchaseInvoice(BuyingController):
else item.deferred_expense_account
)
dummy, amount = self.get_amount_and_base_amount(item, None)
if not item.is_fixed_asset:
dummy, amount = self.get_amount_and_base_amount(item, None)
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:
@@ -1368,14 +1364,7 @@ class PurchaseInvoice(BuyingController):
frappe.db.set(self, "status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Tax Withheld Vouchers",
)
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.update_advance_tax_references(cancel=1)
def update_project(self):

View File

@@ -706,7 +706,6 @@
"label": "Valuation Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1,
"read_only": 1
},
@@ -872,7 +871,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-10-12 03:37:29.032732",
"modified": "2021-11-15 17:04:07.191013",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -1078,7 +1078,7 @@ var select_loyalty_program = function(frm, loyalty_programs) {
]
});
dialog.set_primary_action(__("Set Loyalty Program"), function() {
dialog.set_primary_action(__("Set"), function() {
dialog.hide();
return frappe.call({
method: "frappe.client.set_value",

View File

@@ -1790,8 +1790,6 @@
"width": "50%"
},
{
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate",
"fieldtype": "Float",
"hide_days": 1,
@@ -2047,7 +2045,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2023-01-28 19:45:47.538163",
"modified": "2022-09-16 17:44:22.227332",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2103,4 +2101,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -7,7 +7,17 @@ from frappe import _, msgprint, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
from frappe.utils import (
add_days,
add_months,
cint,
cstr,
flt,
formatdate,
get_link_to_form,
getdate,
nowdate,
)
from six import iteritems
import erpnext
@@ -23,12 +33,10 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import get_account_currency
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain,
reset_depreciation_schedule,
reverse_depreciation_entry_made_after_disposal,
make_depreciation_entry,
)
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController
@@ -1106,20 +1114,18 @@ class SalesInvoice(SellingController):
asset = self.get_asset(item)
if self.is_return:
if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_sale(asset)
self.reset_depreciation_schedule(asset)
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset, item.base_net_amount, item.finance_book
)
asset.db_set("disposal_date", None)
if asset.calculate_depreciation:
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
reset_depreciation_schedule(asset, self.posting_date)
else:
if asset.calculate_depreciation:
depreciate_asset(asset, self.posting_date)
asset.reload()
self.depreciate_asset(asset)
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book
@@ -1187,6 +1193,95 @@ class SalesInvoice(SellingController):
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_sale=self.posting_date)
asset.save()
make_depreciation_entry(asset.name, self.posting_date)
asset.load_from_db()
def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=self.posting_date)
self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
asset.load_from_db()
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_sale(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == posting_date_of_original_invoice:
if not self.sale_was_made_on_original_schedule_date(
asset, schedule, row, posting_date_of_original_invoice
) or self.sale_happens_in_the_future(posting_date_of_original_invoice):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
depreciation_amount = self.get_depreciation_amount_in_je(reverse_journal_entry)
asset.finance_books[0].value_after_depreciation += depreciation_amount
asset.save()
def get_posting_date_of_sales_invoice(self):
return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def sale_was_made_on_original_schedule_date(
self, asset, schedule, row, posting_date_of_original_invoice
):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if orginal_schedule_date == posting_date_of_original_invoice:
return True
return False
def sale_happens_in_the_future(self, posting_date_of_original_invoice):
if posting_date_of_original_invoice > getdate():
return True
return False
def get_depreciation_amount_in_je(self, journal_entry):
if journal_entry.accounts[0].debit_in_account_currency:
return journal_entry.accounts[0].debit_in_account_currency
else:
return journal_entry.accounts[0].credit_in_account_currency
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):
@@ -2092,9 +2187,6 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
update_address(
target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
)
update_address(
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
)
if currency:
target_doc.currency = currency

View File

@@ -31,20 +31,10 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import (
get_qty_after_transaction,
make_stock_entry,
)
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
from erpnext.stock.utils import get_incoming_rate
class TestSalesInvoice(unittest.TestCase):
def setUp(self):
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
create_internal_parties()
setup_accounts()
def make(self):
w = frappe.copy_doc(test_records[0])
w.is_pos = 0
@@ -916,8 +906,7 @@ class TestSalesInvoice(unittest.TestCase):
pos_return.insert()
pos_return.submit()
self.assertEqual(pos_return.get("payments")[0].amount, -500)
self.assertEqual(pos_return.get("payments")[1].amount, -500)
self.assertEqual(pos_return.get("payments")[0].amount, -1000)
def test_pos_change_amount(self):
make_pos_profile(
@@ -1117,46 +1106,6 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
def test_bin_details_of_packed_item(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.item.test_item import make_item
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
make_item("_Packed Item New 1", {"is_stock_item": 1})
make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
si = create_sales_invoice(
item_code="_Test Product Bundle Item New",
update_stock=1,
warehouse="_Test Warehouse - _TC",
transaction_date=add_days(nowdate(), -1),
do_not_submit=1,
)
make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
bin_details = frappe.db.get_value(
"Bin",
{"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
["actual_qty", "projected_qty", "ordered_qty"],
as_dict=1,
)
si.transaction_date = nowdate()
si.save()
packed_item = si.packed_items[0]
self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
def test_pos_si_without_payment(self):
make_pos_profile()
@@ -1738,7 +1687,7 @@ class TestSalesInvoice(unittest.TestCase):
si.save()
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate * 25) / 100 + price_list_rate))
def test_outstanding_amount_after_advance_jv_cancellation(self):
def test_outstanding_amount_after_advance_jv_cancelation(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
)
@@ -1782,7 +1731,7 @@ class TestSalesInvoice(unittest.TestCase):
flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")),
)
def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
def test_outstanding_amount_after_advance_payment_entry_cancelation(self):
pe = frappe.get_doc(
{
"doctype": "Payment Entry",
@@ -2420,6 +2369,29 @@ class TestSalesInvoice(unittest.TestCase):
acc_settings.save()
def test_inter_company_transaction(self):
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
create_internal_customer(
customer_name="_Test Internal Customer",
represents_company="_Test Company 1",
allowed_to_interact_with="Wind Power LLC",
)
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
supplier = frappe.get_doc(
{
"supplier_group": "_Test Supplier Group",
"supplier_name": "_Test Internal Supplier",
"doctype": "Supplier",
"is_internal_supplier": 1,
"represents_company": "Wind Power LLC",
}
)
supplier.append("companies", {"company": "_Test Company 1"})
supplier.insert()
si = create_sales_invoice(
company="Wind Power LLC",
customer="_Test Internal Customer",
@@ -2479,9 +2451,34 @@ class TestSalesInvoice(unittest.TestCase):
se.cancel()
def test_internal_transfer_gl_entry(self):
## Create internal transfer account
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
account = create_account(
account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1",
company="_Test Company with perpetual inventory",
)
frappe.db.set_value(
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
)
customer = create_internal_customer(
"_Test Internal Customer 2",
"_Test Company with perpetual inventory",
"_Test Company with perpetual inventory",
)
create_internal_supplier(
"_Test Internal Supplier 2",
"_Test Company with perpetual inventory",
"_Test Company with perpetual inventory",
)
si = create_sales_invoice(
company="_Test Company with perpetual inventory",
customer="_Test Internal Customer 2",
customer=customer,
debit_to="Debtors - TCP1",
warehouse="Stores - TCP1",
income_account="Sales - TCP1",
@@ -2495,7 +2492,7 @@ class TestSalesInvoice(unittest.TestCase):
si.update_stock = 1
si.items[0].target_warehouse = "Work In Progress - TCP1"
# Add stock to stores for successful stock transfer
# Add stock to stores for succesful stock transfer
make_stock_entry(
target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=1, basic_rate=100
)
@@ -2833,77 +2830,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
def test_internal_transfer_gl_precision_issues(self):
# Make a stock queue of an item with two valuations
# Remove all existing stock for this
if get_stock_balance("_Test Internal Transfer Item", "Stores - TCP1", "2022-04-10"):
create_stock_reconciliation(
item_code="_Test Internal Transfer Item",
warehouse="Stores - TCP1",
qty=0,
rate=0,
company="_Test Company with perpetual inventory",
expense_account="Stock Adjustment - TCP1"
if frappe.get_all("Stock Ledger Entry")
else "Temporary Opening - TCP1",
posting_date="2020-04-10",
posting_time="14:00",
)
make_stock_entry(
item_code="_Test Internal Transfer Item",
target="Stores - TCP1",
qty=9000000,
basic_rate=52.0,
posting_date="2020-04-10",
posting_time="14:00",
)
make_stock_entry(
item_code="_Test Internal Transfer Item",
target="Stores - TCP1",
qty=60000000,
basic_rate=52.349777,
posting_date="2020-04-10",
posting_time="14:00",
)
# Make an internal transfer Sales Invoice Stock in non stock uom to check
# for rounding errors while converting to stock uom
si = create_sales_invoice(
company="_Test Company with perpetual inventory",
customer="_Test Internal Customer 2",
item_code="_Test Internal Transfer Item",
qty=5000000,
uom="Box",
debit_to="Debtors - TCP1",
warehouse="Stores - TCP1",
income_account="Sales - TCP1",
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
currency="INR",
do_not_save=1,
)
# Check GL Entries with precision
si.update_stock = 1
si.items[0].target_warehouse = "Work In Progress - TCP1"
si.items[0].conversion_factor = 10
si.save()
si.submit()
# Check if adjustment entry is created
self.assertTrue(
frappe.db.exists(
"GL Entry",
{
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"remarks": "Rounding gain/loss Entry for Stock Transfer",
},
)
)
def test_item_tax_net_range(self):
item = create_item("T Shirt")
@@ -3352,7 +3278,7 @@ class TestSalesInvoice(unittest.TestCase):
[deferred_account, 2022.47, 0.0, "2019-03-15"],
]
gl_entries = frappe.db.sql(
gl_entries = gl_entries = frappe.db.sql(
"""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
@@ -3757,7 +3683,6 @@ def create_sales_invoice(**args):
"description": args.description or "_Test Item",
"gst_hsn_code": "999800",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"target_warehouse": args.target_warehouse,
"qty": args.qty or 1,
"uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos",
@@ -3770,7 +3695,7 @@ def create_sales_invoice(**args):
"discount_amount": args.discount_amount or 0,
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
"conversion_factor": args.get("conversion_factor", 1),
"conversion_factor": 1,
"incoming_rate": args.incoming_rate or 0,
"batch_no": args.batch_no or None,
},
@@ -3884,34 +3809,6 @@ def get_taxes_and_charges():
]
def create_internal_parties():
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
create_internal_customer(
customer_name="_Test Internal Customer",
represents_company="_Test Company 1",
allowed_to_interact_with="Wind Power LLC",
)
create_internal_customer(
customer_name="_Test Internal Customer 2",
represents_company="_Test Company with perpetual inventory",
allowed_to_interact_with="_Test Company with perpetual inventory",
)
create_internal_supplier(
supplier_name="_Test Internal Supplier",
represents_company="Wind Power LLC",
allowed_to_interact_with="_Test Company 1",
)
create_internal_supplier(
supplier_name="_Test Internal Supplier 2",
represents_company="_Test Company with perpetual inventory",
allowed_to_interact_with="_Test Company with perpetual inventory",
)
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc(
@@ -3934,19 +3831,6 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
return supplier_name
def setup_accounts():
## Create internal transfer account
account = create_account(
account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1",
company="_Test Company with perpetual inventory",
)
frappe.db.set_value(
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
)
def add_taxes(doc):
doc.append(
"taxes",

View File

@@ -812,8 +812,6 @@
"fieldtype": "Currency",
"label": "Incoming Rate (Costing)",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1
},
{
@@ -843,7 +841,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-12-28 16:17:33.484531",
"modified": "2022-08-26 12:06:31.205417",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -36,11 +36,11 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-12-28 23:40:41.479208",
"modified": "2022-09-13 23:40:41.479208",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",
"naming_rule": "Random",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",

View File

@@ -256,7 +256,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers)
if cint(tax_details.round_off_tax_amount):
tax_amount = normal_round(tax_amount)
tax_amount = round(tax_amount)
return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount
@@ -555,20 +555,3 @@ def is_valid_certificate(
valid = True
return valid
def normal_round(number):
"""
Rounds a number to the nearest integer.
:param number: The number to round.
"""
decimal_part = number - int(number)
if decimal_part >= 0.5:
decimal_part = 1
else:
decimal_part = 0
number = int(number) + decimal_part
return number

View File

@@ -298,22 +298,20 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
)
round_off_gle = frappe._dict()
round_off_account_exists = False
round_off_gle = frappe._dict()
for d in gl_map:
if d.account == round_off_account:
round_off_gle = d
if d.debit:
debit_credit_diff -= flt(d.debit)
else:
debit_credit_diff += flt(d.credit)
round_off_account_exists = True
if gl_map[0].voucher_type != "Period Closing Voucher":
for d in gl_map:
if d.account == round_off_account:
round_off_gle = d
if d.debit:
debit_credit_diff -= flt(d.debit) - flt(d.credit)
else:
debit_credit_diff += flt(d.credit)
round_off_account_exists = True
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
gl_map.remove(round_off_gle)
return
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
gl_map.remove(round_off_gle)
return
if not round_off_gle:
for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
@@ -336,6 +334,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
)
update_accounting_dimensions(round_off_gle)
if not round_off_account_exists:
gl_map.append(round_off_gle)

View File

@@ -50,10 +50,6 @@
<div class="col-xs-4"><label>Document No</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Document Date</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.Dt }}</div>
</div>
</div>
<div class="col-xs-4 column-break">
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">

View File

@@ -5,7 +5,7 @@
from collections import OrderedDict
import frappe
from frappe import _, qb, scrub
from frappe import _, scrub
from frappe.utils import cint, cstr, flt, getdate, nowdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -95,9 +95,6 @@ class ReceivablePayableReport(object):
# Get return entries
self.get_return_entries()
# Get Exchange Rate Revaluations
self.get_exchange_rate_revaluations()
self.data = []
for gle in self.gl_entries:
self.update_voucher_balance(gle)
@@ -261,8 +258,7 @@ class ReceivablePayableReport(object):
row.invoice_grand_total = row.invoiced
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
or (row.voucher_no in self.err_journals)
abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision
):
# non-zero oustanding, we must consider this row
@@ -689,10 +685,10 @@ class ReceivablePayableReport(object):
if self.filters.get(scrub(self.party_type)):
select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
doc_currency_fields = "debit as debit_in_account_currency, credit as credit_in_account_currency"
else:
select_fields = "debit, credit"
doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
remarks = ", remarks" if self.filters.get("show_remarks") else ""
@@ -1037,17 +1033,3 @@ class ReceivablePayableReport(object):
"data": {"labels": self.ageing_column_labels, "datasets": rows},
"type": "percentage",
}
def get_exchange_rate_revaluations(self):
je = qb.DocType("Journal Entry")
results = (
qb.from_(je)
.select(je.name)
.where(
(je.company == self.filters.company)
& (je.posting_date.lte(self.filters.report_date))
& (je.voucher_type == "Exchange Rate Revaluation")
)
.run()
)
self.err_journals = [x[0] for x in results] if results else []

View File

@@ -1,51 +1,18 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, today
from frappe.utils import add_days, getdate, today
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute
class TestAccountsReceivable(unittest.TestCase):
def setUp(self):
def test_accounts_receivable(self):
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
frappe.db.sql("delete from `tabJournal Entry` where company='_Test Company 2'")
frappe.db.sql("delete from `tabExchange Rate Revaluation` where company='_Test Company 2'")
self.create_usd_account()
def tearDown(self):
frappe.db.rollback()
def create_usd_account(self):
name = "Debtors USD"
exists = frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
)
if exists:
self.debtors_usd = exists[0].name
else:
debtors = frappe.get_doc(
"Account",
frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
)[0].name,
)
debtors_usd = frappe.new_doc("Account")
debtors_usd.company = debtors.company
debtors_usd.account_name = "Debtors USD"
debtors_usd.account_currency = "USD"
debtors_usd.parent_account = debtors.parent_account
debtors_usd.account_type = debtors.account_type
self.debtors_usd = debtors_usd.save().name
def test_accounts_receivable(self):
filters = {
"company": "_Test Company 2",
"based_on_payment_terms": 1,
@@ -57,7 +24,7 @@ class TestAccountsReceivable(unittest.TestCase):
}
# check invoice grand total and invoiced column's value for 3 payment terms
name = make_sales_invoice().name
name = make_sales_invoice()
report = execute(filters)
expected_data = [[100, 30], [100, 50], [100, 20]]
@@ -98,74 +65,8 @@ class TestAccountsReceivable(unittest.TestCase):
],
)
@change_settings(
"Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1}
)
def test_exchange_revaluation_for_party(self):
"""
Exchange Revaluation for party on Receivable/Payable shoule be included
"""
company = "_Test Company 2"
customer = "_Test Customer 2"
# Using Exchange Gain/Loss account for unrealized as well.
company_doc = frappe.get_doc("Company", company)
company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account
company_doc.save()
si = make_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si.currency = "USD"
si.conversion_rate = 0.90
si.debit_to = self.debtors_usd
si = si.save().submit()
# Exchange Revaluation
err = frappe.new_doc("Exchange Rate Revaluation")
err.company = company
err.posting_date = today()
accounts = err.get_accounts_data()
err.extend("accounts", accounts)
err.accounts[0].new_exchange_rate = 0.95
row = err.accounts[0]
row.new_balance_in_base_currency = flt(
row.new_exchange_rate * flt(row.balance_in_account_currency)
)
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
err.set_total_gain_loss()
err = err.save().submit()
# Submit JV for ERR
jv = frappe.get_doc(err.make_jv_entry())
jv = jv.save()
for x in jv.accounts:
x.cost_center = get_default_cost_center(jv.company)
jv.submit()
filters = {
"company": company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
report = execute(filters)
expected_data_for_err = [0, -5, 0, 5]
row = [x for x in report[1] if x.voucher_type == jv.doctype and x.voucher_no == jv.name][0]
self.assertEqual(
expected_data_for_err,
[
row.invoiced,
row.paid,
row.credit_note,
row.outstanding,
],
)
def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
def make_sales_invoice():
frappe.set_user("Administrator")
si = create_sales_invoice(
@@ -180,26 +81,22 @@ def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
do_not_save=1,
)
if not no_payment_schedule:
si.append(
"payment_schedule",
dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30),
)
si.append(
"payment_schedule",
dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50),
)
si.append(
"payment_schedule",
dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20),
)
si.append(
"payment_schedule",
dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30),
)
si.append(
"payment_schedule",
dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50),
)
si.append(
"payment_schedule",
dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20),
)
si = si.save()
si.submit()
if not do_not_submit:
si = si.submit()
return si
return si.name
def make_payment(docname):

View File

@@ -135,34 +135,6 @@ def get_assets(filters):
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != ''
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
gle.debit
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
else
0
end), 0) as depreciation_eliminated_during_the_period,
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
gle.debit
else
0
end), 0) as depreciation_amount_during_the_period
from `tabGL Entry` gle
join `tabAsset` a on
gle.against_voucher = a.name
join `tabAsset Category Account` aca on
aca.parent = a.asset_category and aca.company_name = %(company)s
join `tabCompany` company on
company.name = %(company)s
where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
0

View File

@@ -22,7 +22,8 @@ def get_columns():
{
"label": _("Payment Document Type"),
"fieldname": "payment_document_type",
"fieldtype": "Data",
"fieldtype": "Link",
"options": "Doctype",
"width": 130,
},
{
@@ -32,15 +33,15 @@ def get_columns():
"options": "payment_document_type",
"width": 140,
},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
{"label": _("Cheque/Reference No"), "fieldname": "cheque_no", "width": 120},
{"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 120},
{"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 100},
{
"label": _("Against Account"),
"fieldname": "against",
"fieldtype": "Link",
"options": "Account",
"width": 200,
"width": 170,
},
{"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 120},
]

View File

@@ -9,7 +9,6 @@ from six import iteritems
from erpnext.accounts.report.financial_statements import (
get_columns,
get_cost_centers_with_children,
get_data,
get_filtered_list_for_consolidated_report,
get_period_list,
@@ -162,11 +161,10 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
total = 0
for period in period_list:
start_date = get_start_date(period, accumulated_values, company)
filters.start_date = start_date
filters.end_date = period["to_date"]
filters.account_type = account_type
amount = get_account_type_based_gl_data(company, filters)
amount = get_account_type_based_gl_data(
company, start_date, period["to_date"], account_type, filters
)
if amount and account_type == "Depreciation":
amount *= -1
@@ -178,7 +176,7 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
return data
def get_account_type_based_gl_data(company, filters=None):
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None):
cond = ""
filters = frappe._dict(filters or {})
@@ -194,21 +192,17 @@ def get_account_type_based_gl_data(company, filters=None):
frappe.db.escape(cstr(filters.finance_book))
)
if filters.get("cost_center"):
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
cond += " and cost_center in %(cost_center)s"
gl_sum = frappe.db.sql_list(
"""
select sum(credit) - sum(debit)
from `tabGL Entry`
where company=%(company)s and posting_date >= %(start_date)s and posting_date <= %(end_date)s
where company=%s and posting_date >= %s and posting_date <= %s
and voucher_type != 'Period Closing Voucher'
and account in ( SELECT name FROM tabAccount WHERE account_type = %(account_type)s) {cond}
and account in ( SELECT name FROM tabAccount WHERE account_type = %s) {cond}
""".format(
cond=cond
),
filters,
(company, start_date, end_date, account_type),
)
return gl_sum[0] if gl_sum and gl_sum[0] else 0

View File

@@ -268,12 +268,10 @@ def get_cash_flow_data(fiscal_year, companies, filters):
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
data = {}
total = 0
filters.account_type = account_type
filters.start_date = fiscal_year.year_start_date
filters.end_date = fiscal_year.year_end_date
for company in companies:
amount = get_account_type_based_gl_data(company, filters)
amount = get_account_type_based_gl_data(
company, fiscal_year.year_start_date, fiscal_year.year_end_date, account_type, filters
)
if amount and account_type == "Depreciation":
amount *= -1
@@ -535,13 +533,12 @@ def get_accounts(root_type, companies):
],
filters={"company": company, "root_type": root_type},
):
if account.account_number:
account_key = account.account_number + "-" + account.account_name
else:
account_key = account.account_name
if account_key not in added_accounts:
if account.account_name not in added_accounts:
accounts.append(account)
if account.account_number:
account_key = account.account_number + "-" + account.account_name
else:
account_key = account.account_name
added_accounts.append(account_key)
return accounts

View File

@@ -27,7 +27,6 @@ class PartyLedgerSummaryReport(object):
)
self.get_gl_entries()
self.get_additional_columns()
self.get_return_invoices()
self.get_party_adjustment_amounts()
@@ -35,42 +34,6 @@ class PartyLedgerSummaryReport(object):
data = self.get_data()
return columns, data
def get_additional_columns(self):
"""
Additional Columns for 'User Permission' based access control
"""
from frappe import qb
if self.filters.party_type == "Customer":
self.territories = frappe._dict({})
self.customer_group = frappe._dict({})
customer = qb.DocType("Customer")
result = (
frappe.qb.from_(customer)
.select(
customer.name, customer.territory, customer.customer_group, customer.default_sales_partner
)
.where((customer.disabled == 0))
.run(as_dict=True)
)
for x in result:
self.territories[x.name] = x.territory
self.customer_group[x.name] = x.customer_group
else:
self.supplier_group = frappe._dict({})
supplier = qb.DocType("Supplier")
result = (
frappe.qb.from_(supplier)
.select(supplier.name, supplier.supplier_group)
.where((supplier.disabled == 0))
.run(as_dict=True)
)
for x in result:
self.supplier_group[x.name] = x.supplier_group
def get_columns(self):
columns = [
{
@@ -154,35 +117,6 @@ class PartyLedgerSummaryReport(object):
},
]
# Hidden columns for handling 'User Permissions'
if self.filters.party_type == "Customer":
columns += [
{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
"hidden": 1,
},
{
"label": _("Customer Group"),
"fieldname": "customer_group",
"fieldtype": "Link",
"options": "Customer Group",
"hidden": 1,
},
]
else:
columns += [
{
"label": _("Supplier Group"),
"fieldname": "supplier_group",
"fieldtype": "Link",
"options": "Supplier Group",
"hidden": 1,
}
]
return columns
def get_data(self):
@@ -210,12 +144,6 @@ class PartyLedgerSummaryReport(object):
),
)
if self.filters.party_type == "Customer":
self.party_data[gle.party].update({"territory": self.territories.get(gle.party)})
self.party_data[gle.party].update({"customer_group": self.customer_group.get(gle.party)})
else:
self.party_data[gle.party].update({"supplier_group": self.supplier_group.get(gle.party)})
amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
self.party_data[gle.party].closing_balance += amount

View File

@@ -378,14 +378,15 @@ class Deferred_Revenue_and_Expense_Report(object):
ret += [{}]
# add total row
if self.filters.type == "Revenue":
total_row = frappe._dict({"name": "Total Deferred Income"})
elif self.filters.type == "Expense":
total_row = frappe._dict({"name": "Total Deferred Expense"})
if ret is not []:
if self.filters.type == "Revenue":
total_row = frappe._dict({"name": "Total Deferred Income"})
elif self.filters.type == "Expense":
total_row = frappe._dict({"name": "Total Deferred Expense"})
for idx, period in enumerate(self.period_list, 0):
total_row[period.key] = self.period_total[idx].total
ret.append(total_row)
for idx, period in enumerate(self.period_list, 0):
total_row[period.key] = self.period_total[idx].total
ret.append(total_row)
return ret

View File

@@ -25,8 +25,8 @@
<thead>
<tr>
<th style="width: 12%">{%= __("Date") %}</th>
<th style="width: 15%">{%= __("Reference") %}</th>
<th style="width: 25%">{%= __("Remarks") %}</th>
<th style="width: 15%">{%= __("Ref") %}</th>
<th style="width: 25%">{%= __("Party") %}</th>
<th style="width: 15%">{%= __("Debit") %}</th>
<th style="width: 15%">{%= __("Credit") %}</th>
<th style="width: 18%">{%= __("Balance (Dr - Cr)") %}</th>
@@ -38,28 +38,23 @@
{% if(data[i].posting_date) { %}
<td>{%= frappe.datetime.str_to_user(data[i].posting_date) %}</td>
<td>{%= data[i].voucher_type %}
<br>{%= data[i].voucher_no %}
</td>
{% var longest_word = cstr(data[i].remarks).split(" ").reduce((longest, word) => word.length > longest.length ? word : longest, ""); %}
<td {% if longest_word.length > 45 %} class="overflow-wrap-anywhere" {% endif %}>
<span>
<br>{%= data[i].voucher_no %}</td>
<td>
{% if(!(filters.party || filters.account)) { %}
{%= data[i].party || data[i].account %}
<br>
{% } %}
{{ __("Against") }}: {%= data[i].against %}
<br>{%= __("Remarks") %}: {%= data[i].remarks %}
{% if(data[i].bill_no) { %}
<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
{% } %}
</span>
</td>
<td style="text-align: right">
{%= format_currency(data[i].debit, filters.presentation_currency) %}
</td>
<td style="text-align: right">
{%= format_currency(data[i].credit, filters.presentation_currency) %}
</td>
</td>
<td style="text-align: right">
{%= format_currency(data[i].debit, filters.presentation_currency) %}</td>
<td style="text-align: right">
{%= format_currency(data[i].credit, filters.presentation_currency) %}</td>
{% } else { %}
<td></td>
<td></td>

View File

@@ -282,7 +282,7 @@ def get_conditions(filters):
):
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
conditions.append("(posting_date <=%(to_date)s)")
if filters.get("project"):
conditions.append("project in %(project)s")

View File

@@ -3,8 +3,7 @@
import frappe
from frappe import _, qb, scrub
from frappe.query_builder import Order
from frappe import _, scrub
from frappe.utils import cint, flt
from erpnext.controllers.queries import get_match_cond
@@ -364,16 +363,15 @@ def get_column_names():
class GrossProfitGenerator(object):
def __init__(self, filters=None):
self.sle = {}
self.data = []
self.average_buying_rate = {}
self.filters = frappe._dict(filters)
self.load_invoice_items()
self.get_delivery_notes()
if filters.group_by == "Invoice":
self.group_items_by_invoice()
self.load_stock_ledger_entries()
self.load_product_bundle()
self.load_non_stock_items()
self.get_returned_invoice_items()
@@ -537,22 +535,6 @@ class GrossProfitGenerator(object):
return flt(buying_amount, self.currency_precision)
def calculate_buying_amount_from_sle(self, row, my_sle, parenttype, parent, item_row, item_code):
for i, sle in enumerate(my_sle):
# find the stock valution rate from stock ledger entry
if (
sle.voucher_type == parenttype
and parent == sle.voucher_no
and sle.voucher_detail_no == item_row
):
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
if previous_stock_value:
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
return 0.0
def get_buying_amount(self, row, item_code):
# IMP NOTE
# stock_ledger_entries should already be filtered by item_code and warehouse and
@@ -563,54 +545,29 @@ class GrossProfitGenerator(object):
return flt(row.qty) * item_rate
else:
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
my_sle = self.sle.get((item_code, row.warehouse))
if (row.update_stock or row.dn_detail) and my_sle:
parenttype, parent = row.parenttype, row.parent
if row.dn_detail:
parenttype, parent = "Delivery Note", row.delivery_note
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, row.item_row, item_code
)
elif self.delivery_notes.get((row.parent, row.item_code), None):
# check if Invoice has delivery notes
dn = self.delivery_notes.get((row.parent, row.item_code))
parenttype, parent, item_row, warehouse = (
"Delivery Note",
dn["delivery_note"],
dn["item_row"],
dn["warehouse"],
)
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
elif row.sales_order and row.so_detail:
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
if incoming_amount:
return incoming_amount
for i, sle in enumerate(my_sle):
# find the stock valution rate from stock ledger entry
if (
sle.voucher_type == parenttype
and parent == sle.voucher_no
and sle.voucher_detail_no == row.item_row
):
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
if previous_stock_value:
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
from frappe.query_builder.functions import Sum
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
query = (
frappe.qb.from_(delivery_note_item)
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
.where(delivery_note_item.docstatus == 1)
.where(delivery_note_item.item_code == item_code)
.where(delivery_note_item.against_sales_order == sales_order)
.where(delivery_note_item.so_detail == so_detail)
.groupby(delivery_note_item.item_code)
)
incoming_amount = query.run()
return flt(incoming_amount[0][0]) if incoming_amount else 0
return 0.0
def get_average_buying_rate(self, row, item_code):
args = row
@@ -687,8 +644,7 @@ class GrossProfitGenerator(object):
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
@@ -711,29 +667,6 @@ class GrossProfitGenerator(object):
as_dict=1,
)
def get_delivery_notes(self):
self.delivery_notes = frappe._dict({})
if self.si_list:
invoices = [x.parent for x in self.si_list]
dni = qb.DocType("Delivery Note Item")
delivery_notes = (
qb.from_(dni)
.select(
dni.against_sales_invoice.as_("sales_invoice"),
dni.item_code,
dni.warehouse,
dni.parent.as_("delivery_note"),
dni.name.as_("item_row"),
)
.where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices)))
.groupby(dni.against_sales_invoice, dni.item_code)
.orderby(dni.creation, order=Order.desc)
.run(as_dict=True)
)
for entry in delivery_notes:
self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry
def group_items_by_invoice(self):
"""
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
@@ -837,36 +770,24 @@ class GrossProfitGenerator(object):
"Item", item_code, ["item_name", "description", "item_group", "brand"]
)
def get_stock_ledger_entries(self, item_code, warehouse):
if item_code and warehouse:
if (item_code, warehouse) not in self.sle:
sle = qb.DocType("Stock Ledger Entry")
res = (
qb.from_(sle)
.select(
sle.item_code,
sle.voucher_type,
sle.voucher_no,
sle.voucher_detail_no,
sle.stock_value,
sle.warehouse,
sle.actual_qty.as_("qty"),
)
.where(
(sle.company == self.filters.company)
& (sle.item_code == item_code)
& (sle.warehouse == warehouse)
& (sle.is_cancelled == 0)
)
.orderby(sle.item_code)
.orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
.run(as_dict=True)
)
def load_stock_ledger_entries(self):
res = frappe.db.sql(
"""select item_code, voucher_type, voucher_no,
voucher_detail_no, stock_value, warehouse, actual_qty as qty
from `tabStock Ledger Entry`
where company=%(company)s and is_cancelled = 0
order by
item_code desc, warehouse desc, posting_date desc,
posting_time desc, creation desc""",
self.filters,
as_dict=True,
)
self.sle = {}
for r in res:
if (r.item_code, r.warehouse) not in self.sle:
self.sle[(r.item_code, r.warehouse)] = []
self.sle[(item_code, warehouse)] = res
return self.sle[(item_code, warehouse)]
return []
self.sle[(r.item_code, r.warehouse)].append(r)
def load_product_bundle(self):
self.product_bundles = {}

View File

@@ -1,382 +0,0 @@
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, nowdate
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.gross_profit.gross_profit import execute
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestGrossProfit(FrappeTestCase):
def setUp(self):
self.create_company()
self.create_item()
self.create_bundle()
self.create_customer()
self.create_sales_invoice()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def create_company(self):
company_name = "_Test Gross Profit"
abbr = "_GP"
if frappe.db.exists("Company", company_name):
company = frappe.get_doc("Company", company_name)
else:
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": company_name,
"country": "India",
"default_currency": "INR",
"create_chart_of_accounts_based_on": "Standard Template",
"chart_of_accounts": "Standard",
}
)
company = company.save()
self.company = company.name
self.cost_center = company.cost_center
self.warehouse = "Stores - " + abbr
self.finished_warehouse = "Finished Goods - " + abbr
self.income_account = "Sales - " + abbr
self.expense_account = "Cost of Goods Sold - " + abbr
self.debit_to = "Debtors - " + abbr
self.creditors = "Creditors - " + abbr
def create_item(self):
item = create_item(
item_code="_Test GP Item", is_stock_item=1, company=self.company, warehouse=self.warehouse
)
self.item = item if isinstance(item, str) else item.item_code
def create_bundle(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
item2 = create_item(
item_code="_Test GP Item 2", is_stock_item=1, company=self.company, warehouse=self.warehouse
)
self.item2 = item2 if isinstance(item2, str) else item2.item_code
# This will be parent item
bundle = create_item(
item_code="_Test GP bundle", is_stock_item=0, company=self.company, warehouse=self.warehouse
)
self.bundle = bundle if isinstance(bundle, str) else bundle.item_code
# Create Product Bundle
self.product_bundle = make_product_bundle(parent=self.bundle, items=[self.item, self.item2])
def create_customer(self):
name = "_Test GP Customer"
if frappe.db.exists("Customer", name):
self.customer = name
else:
customer = frappe.new_doc("Customer")
customer.customer_name = name
customer.type = "Individual"
customer.save()
self.customer = customer.name
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
sinv = create_sales_invoice(
qty=qty,
rate=rate,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return sinv
def create_delivery_note(
self, item=None, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in Delivery Note
"""
dnote = create_delivery_note(
company=self.company,
customer=self.customer,
currency="INR",
item=item or self.item,
qty=qty,
rate=rate,
cost_center=self.cost_center,
warehouse=self.warehouse,
return_against=None,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return dnote
def clear_old_entries(self):
doctype_list = [
"Sales Invoice",
"GL Entry",
"Stock Entry",
"Stock Ledger Entry",
"Delivery Note",
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def test_invoice_without_only_delivery_note(self):
"""
Test buying amount for Invoice without `update_stock` flag set but has Delivery Note
"""
se = make_stock_entry(
company=self.company,
item_code=self.item,
target=self.warehouse,
qty=1,
basic_rate=100,
do_not_submit=True,
)
item = se.items[0]
se.append(
"items",
{
"item_code": item.item_code,
"s_warehouse": item.s_warehouse,
"t_warehouse": item.t_warehouse,
"qty": 1,
"basic_rate": 200,
"conversion_factor": item.conversion_factor or 1.0,
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"cost_center": item.cost_center,
"expense_account": item.expense_account,
},
)
se = se.save().submit()
sinv = create_sales_invoice(
qty=1,
rate=100,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
income_account=self.income_account,
expense_account=self.expense_account,
)
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
# Without Delivery Note, buying rate should be 150
expected_entry_without_dn = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 150.0,
"selling_amount": 100.0,
"buying_amount": 150.0,
"gross_profit": -50.0,
"gross_profit_%": -50.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry_without_dn, gp_entry[0])
# make delivery note
dn = make_delivery_note(sinv.name)
dn.items[0].qty = 1
dn = dn.save().submit()
columns, data = execute(filters=filters)
# Without Delivery Note, buying rate should be 100
expected_entry_with_dn = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 100.0,
"selling_amount": 100.0,
"buying_amount": 100.0,
"gross_profit": 0.0,
"gross_profit_%": 0.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry_with_dn, gp_entry[0])
def test_bundled_delivery_note_with_different_warehouses(self):
"""
Test Delivery Note with bundled item. Packed Item from the bundle having different warehouses
"""
se = make_stock_entry(
company=self.company,
item_code=self.item,
target=self.warehouse,
qty=1,
basic_rate=100,
do_not_submit=True,
)
item = se.items[0]
se.append(
"items",
{
"item_code": self.item2,
"s_warehouse": "",
"t_warehouse": self.finished_warehouse,
"qty": 1,
"basic_rate": 100,
"conversion_factor": item.conversion_factor or 1.0,
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"cost_center": item.cost_center,
"expense_account": item.expense_account,
},
)
se = se.save().submit()
# Make a Delivery note with Product bundle
# Packed Items will have different warehouses
dnote = self.create_delivery_note(item=self.bundle, qty=1, rate=200, do_not_submit=True)
dnote.packed_items[1].warehouse = self.finished_warehouse
dnote = dnote.submit()
# make Sales Invoice for above delivery note
sinv = make_sales_invoice(dnote.name)
sinv = sinv.save().submit()
filters = frappe._dict(
company=self.company,
from_date=nowdate(),
to_date=nowdate(),
group_by="Invoice",
sales_invoice=sinv.name,
)
columns, data = execute(filters=filters)
self.assertGreater(len(data), 0)
def test_order_connected_dn_and_inv(self):
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
"""
Test gp calculation when invoice and delivery note aren't directly connected.
SO -- INV
|
DN
"""
se = make_stock_entry(
company=self.company,
item_code=self.item,
target=self.warehouse,
qty=3,
basic_rate=100,
do_not_submit=True,
)
item = se.items[0]
se.append(
"items",
{
"item_code": item.item_code,
"s_warehouse": item.s_warehouse,
"t_warehouse": item.t_warehouse,
"qty": 10,
"basic_rate": 200,
"conversion_factor": item.conversion_factor or 1.0,
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"cost_center": item.cost_center,
"expense_account": item.expense_account,
},
)
se = se.save().submit()
so = make_sales_order(
customer=self.customer,
company=self.company,
warehouse=self.warehouse,
item=self.item,
qty=4,
do_not_save=False,
do_not_submit=False,
)
from erpnext.selling.doctype.sales_order.sales_order import (
make_delivery_note,
make_sales_invoice,
)
make_delivery_note(so.name).submit()
sinv = make_sales_invoice(so.name).submit()
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 4.0,
"avg._selling_rate": 100.0,
"valuation_rate": 125.0,
"selling_amount": 400.0,
"buying_amount": 500.0,
"gross_profit": -100.0,
"gross_profit_%": -25.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])

View File

@@ -53,6 +53,9 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
item_details = get_item_details()
for d in item_list:
if not d.stock_qty:
continue
item_record = item_details.get(d.item_code)
purchase_receipt = None
@@ -91,7 +94,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
"expense_account": expense_account,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
"rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
"rate": d.base_net_amount / d.stock_qty,
"amount": d.base_net_amount,
}
)

View File

@@ -232,12 +232,12 @@ def get_conditions(filters):
conditions += (
common_condition
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
)
else:
conditions += (
common_condition
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
)
return conditions

View File

@@ -390,12 +390,12 @@ def get_conditions(filters):
conditions += (
common_condition
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
)
else:
conditions += (
common_condition
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
)
return conditions

View File

@@ -63,6 +63,24 @@ frappe.query_reports["Supplier Ledger Summary"] = {
"fieldtype": "Link",
"options": "Payment Terms Template"
},
{
"fieldname":"territory",
"label": __("Territory"),
"fieldtype": "Link",
"options": "Territory"
},
{
"fieldname":"sales_partner",
"label": __("Sales Partner"),
"fieldtype": "Link",
"options": "Sales Partner"
},
{
"fieldname":"sales_person",
"label": __("Sales Person"),
"fieldtype": "Link",
"options": "Sales Person"
},
{
"fieldname":"tax_id",
"label": __("Tax Id"),

View File

@@ -28,7 +28,7 @@ def get_currency(filters):
filters["presentation_currency"] if filters.get("presentation_currency") else company_currency
)
report_date = filters.get("to_date") or filters.get("period_end_date")
report_date = filters.get("to_date")
if not report_date:
fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"]

View File

@@ -835,12 +835,15 @@ def remove_return_pos_invoices(party_type, party, invoice_list):
else:
return invoice_list
invoice_list = [x for x in invoice_list if x.voucher_no not in return_pos]
# remove pos return invoices from invoice_list
for idx, inv in enumerate(invoice_list, 0):
if inv.voucher_no in return_pos:
del invoice_list[idx]
return invoice_list
def get_outstanding_invoices(party_type, party, account, company, condition=None, filters=None):
def get_outstanding_invoices(party_type, party, account, condition=None, filters=None):
outstanding_invoices = []
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
@@ -892,73 +895,61 @@ def get_outstanding_invoices(party_type, party, account, company, condition=None
invoice_list = remove_return_pos_invoices(party_type, party, invoice_list)
if invoice_list:
invoices = [d.voucher_no for d in invoice_list]
payment_entries = frappe.db.sql(
"""
select against_voucher_type, against_voucher,
ifnull(sum({payment_dr_or_cr}), 0) as payment_amount
from `tabGL Entry`
where
company = %(company)s
and party_type = %(party_type)s and party = %(party)s
and account = %(account)s
and {payment_dr_or_cr} > 0
and ifnull(against_voucher, '') != ''
and is_cancelled=0
and against_voucher in %(invoices)s
group by against_voucher_type, against_voucher
""".format(
payment_dr_or_cr=payment_dr_or_cr,
),
{
"company": company,
"party_type": party_type,
"party": party,
"account": account,
"invoices": invoices,
},
as_dict=True,
)
payment_entries = frappe.db.sql(
"""
select against_voucher_type, against_voucher,
ifnull(sum({payment_dr_or_cr}), 0) as payment_amount
from `tabGL Entry`
where party_type = %(party_type)s and party = %(party)s
and account = %(account)s
and {payment_dr_or_cr} > 0
and against_voucher is not null and against_voucher != ''
and is_cancelled=0
group by against_voucher_type, against_voucher
""".format(
payment_dr_or_cr=payment_dr_or_cr
),
{"party_type": party_type, "party": party, "account": account},
as_dict=True,
)
pe_map = frappe._dict()
for d in payment_entries:
pe_map.setdefault((d.against_voucher_type, d.against_voucher), d.payment_amount)
pe_map = frappe._dict()
for d in payment_entries:
pe_map.setdefault((d.against_voucher_type, d.against_voucher), d.payment_amount)
for d in invoice_list:
payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0)
outstanding_amount = flt(d.invoice_amount - payment_amount, precision)
if outstanding_amount > 0.5 / (10**precision):
if (
filters
and filters.get("outstanding_amt_greater_than")
and not (
outstanding_amount >= filters.get("outstanding_amt_greater_than")
and outstanding_amount <= filters.get("outstanding_amt_less_than")
for d in invoice_list:
payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0)
outstanding_amount = flt(d.invoice_amount - payment_amount, precision)
if outstanding_amount > 0.5 / (10**precision):
if (
filters
and filters.get("outstanding_amt_greater_than")
and not (
outstanding_amount >= filters.get("outstanding_amt_greater_than")
and outstanding_amount <= filters.get("outstanding_amt_less_than")
)
):
continue
if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
outstanding_invoices.append(
frappe._dict(
{
"voucher_no": d.voucher_no,
"voucher_type": d.voucher_type,
"posting_date": d.posting_date,
"invoice_amount": flt(d.invoice_amount),
"payment_amount": payment_amount,
"outstanding_amount": outstanding_amount,
"due_date": d.due_date,
"currency": d.currency,
}
)
):
continue
if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
outstanding_invoices.append(
frappe._dict(
{
"voucher_no": d.voucher_no,
"voucher_type": d.voucher_type,
"posting_date": d.posting_date,
"invoice_amount": flt(d.invoice_amount),
"payment_amount": payment_amount,
"outstanding_amount": outstanding_amount,
"due_date": d.due_date,
"currency": d.currency,
}
)
)
outstanding_invoices = sorted(
outstanding_invoices, key=lambda k: k["due_date"] or getdate(nowdate())
)
)
outstanding_invoices = sorted(
outstanding_invoices, key=lambda k: k["due_date"] or getdate(nowdate())
)
return outstanding_invoices

View File

@@ -132,10 +132,6 @@ frappe.ui.form.on('Asset', {
}, __("Manage"));
}
if (frm.doc.depr_entry_posting_status === "Failed") {
frm.trigger("set_depr_posting_failure_alert");
}
frm.trigger("setup_chart");
}
@@ -146,19 +142,6 @@ frappe.ui.form.on('Asset', {
}
},
set_depr_posting_failure_alert: function (frm) {
const alert = `
<div class="row">
<div class="col-xs-12 col-sm-6">
<span class="indicator whitespace-nowrap red">
<span>Failed to post depreciation entries</span>
</span>
</div>
</div>`;
frm.dashboard.set_headline_alert(alert);
},
toggle_reference_doc: function(frm) {
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
frm.set_df_property('purchase_invoice', 'read_only', 1);
@@ -201,58 +184,39 @@ frappe.ui.form.on('Asset', {
})
},
setup_chart: async function(frm) {
if(frm.doc.finance_books.length > 1) {
return
}
var x_intervals = [frappe.format(frm.doc.purchase_date, { fieldtype: 'Date' })];
setup_chart: function(frm) {
var x_intervals = [frm.doc.purchase_date];
var asset_values = [frm.doc.gross_purchase_amount];
var last_depreciation_date = frm.doc.purchase_date;
if(frm.doc.calculate_depreciation) {
if(frm.doc.opening_accumulated_depreciation) {
var depreciation_date = frappe.datetime.add_months(
frm.doc.finance_books[0].depreciation_start_date,
-1 * frm.doc.finance_books[0].frequency_of_depreciation
);
x_intervals.push(frappe.format(depreciation_date, { fieldtype: 'Date' }));
asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
}
if(frm.doc.opening_accumulated_depreciation) {
last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date,
-1*frm.doc.frequency_of_depreciation);
$.each(frm.doc.schedules || [], function(i, v) {
x_intervals.push(frappe.format(v.schedule_date, { fieldtype: 'Date' }));
var asset_value = flt(frm.doc.gross_purchase_amount - v.accumulated_depreciation_amount, precision('gross_purchase_amount'));
if(v.journal_entry) {
asset_values.push(asset_value);
} else {
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
asset_values.push(null);
} else {
asset_values.push(asset_value);
}
}
});
} else {
if(frm.doc.opening_accumulated_depreciation) {
x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
}
let depr_entries = (await frappe.call({
method: "get_manual_depreciation_entries",
doc: frm.doc,
})).message;
$.each(depr_entries || [], function(i, v) {
x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
let last_asset_value = asset_values[asset_values.length - 1]
asset_values.push(flt(last_asset_value - v.value, precision('gross_purchase_amount')));
});
x_intervals.push(last_depreciation_date);
asset_values.push(flt(frm.doc.gross_purchase_amount) -
flt(frm.doc.opening_accumulated_depreciation));
}
$.each(frm.doc.schedules || [], function(i, v) {
x_intervals.push(v.schedule_date);
var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount);
if(v.journal_entry) {
last_depreciation_date = v.schedule_date;
asset_values.push(asset_value);
} else {
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
asset_values.push(null);
} else {
asset_values.push(asset_value)
}
}
});
if(in_list(["Scrapped", "Sold"], frm.doc.status)) {
x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: 'Date' }));
x_intervals.push(frm.doc.disposal_date);
asset_values.push(0);
last_depreciation_date = frm.doc.disposal_date;
}
frm.dashboard.render_graph({
@@ -296,6 +260,10 @@ frappe.ui.form.on('Asset', {
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
},
opening_accumulated_depreciation: function(frm) {
erpnext.asset.set_accumulated_depreciation(frm);
},
make_schedules_editable: function(frm) {
if (frm.doc.finance_books) {
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
@@ -416,11 +384,7 @@ frappe.ui.form.on('Asset', {
set_values_from_purchase_doc: function(frm, doctype, purchase_doc) {
frm.set_value('company', purchase_doc.company);
if (purchase_doc.bill_date) {
frm.set_value('purchase_date', purchase_doc.bill_date);
} else {
frm.set_value('purchase_date', purchase_doc.posting_date);
}
frm.set_value('purchase_date', purchase_doc.posting_date);
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
doctype_field = frappe.scrub(doctype)
@@ -515,23 +479,19 @@ frappe.ui.form.on('Depreciation Schedule', {
},
depreciation_amount: function(frm, cdt, cdn) {
erpnext.asset.set_accumulated_depreciation(frm, locals[cdt][cdn].finance_book_id);
erpnext.asset.set_accumulated_depreciation(frm);
}
});
})
erpnext.asset.set_accumulated_depreciation = function(frm, finance_book_id) {
var depreciation_method = frm.doc.finance_books[Number(finance_book_id) - 1].depreciation_method;
if(depreciation_method != "Manual") return;
erpnext.asset.set_accumulated_depreciation = function(frm) {
if(frm.doc.depreciation_method != "Manual") return;
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
$.each(frm.doc.schedules || [], function(i, row) {
if (row.finance_book_id === finance_book_id) {
accumulated_depreciation += flt(row.depreciation_amount);
frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation);
};
accumulated_depreciation += flt(row.depreciation_amount);
frappe.model.set_value(row.doctype, row.name,
"accumulated_depreciation_amount", accumulated_depreciation);
})
};

View File

@@ -68,7 +68,6 @@
"column_break_51",
"purchase_receipt_amount",
"default_finance_book",
"depr_entry_posting_status",
"amended_from"
],
"fields": [
@@ -474,16 +473,6 @@
"fieldname": "section_break_36",
"fieldtype": "Section Break",
"label": "Finance Books"
},
{
"fieldname": "depr_entry_posting_status",
"fieldtype": "Select",
"hidden": 1,
"label": "Depreciation Entry Posting Status",
"no_copy": 1,
"options": "\nSuccessful\nFailed",
"print_hide": 1,
"read_only": 1
}
],
"idx": 72,
@@ -498,21 +487,15 @@
{
"group": "Repair",
"link_doctype": "Asset Repair",
"link_fieldname": "asset"
"link_fieldname": "asset_name"
},
{
"group": "Value",
"link_doctype": "Asset Value Adjustment",
"link_fieldname": "asset"
},
{
"group": "Journal Entry",
"link_doctype": "Journal Entry",
"link_fieldname": "reference_name",
"table_fieldname": "accounts"
}
],
"modified": "2023-01-31 01:03:09.467817",
"modified": "2022-07-20 16:22:44.437579",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -27,7 +27,6 @@ from erpnext.accounts.general_ledger import make_reverse_gl_entries
from erpnext.assets.doctype.asset.depreciation import (
get_depreciation_accounts,
get_disposal_account_and_cost_center,
is_last_day_of_the_month,
)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.controllers.accounts_controller import AccountsController
@@ -80,59 +79,18 @@ class Asset(AccountsController):
_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)
)
def prepare_depreciation_data(self, date_of_disposal=None, date_of_return=None):
def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None):
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
if self.should_prepare_depreciation_schedule():
self.make_depreciation_schedule(date_of_disposal)
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
self.make_depreciation_schedule(date_of_sale)
self.set_accumulated_depreciation(date_of_sale, date_of_return)
else:
self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)
def should_prepare_depreciation_schedule(self):
if not self.get("schedules"):
return True
old_asset_doc = self.get_doc_before_save()
if not old_asset_doc:
return True
have_asset_details_been_modified = (
old_asset_doc.gross_purchase_amount != self.gross_purchase_amount
or old_asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
or old_asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
)
if have_asset_details_been_modified:
return True
manual_fb_idx = -1
for d in self.finance_books:
if d.depreciation_method == "Manual":
manual_fb_idx = d.idx - 1
no_manual_depr_or_have_manual_depr_details_been_modified = (
manual_fb_idx == -1
or old_asset_doc.finance_books[manual_fb_idx].total_number_of_depreciations
!= self.finance_books[manual_fb_idx].total_number_of_depreciations
or old_asset_doc.finance_books[manual_fb_idx].frequency_of_depreciation
!= self.finance_books[manual_fb_idx].frequency_of_depreciation
or old_asset_doc.finance_books[manual_fb_idx].depreciation_start_date
!= getdate(self.finance_books[manual_fb_idx].depreciation_start_date)
or old_asset_doc.finance_books[manual_fb_idx].expected_value_after_useful_life
!= self.finance_books[manual_fb_idx].expected_value_after_useful_life
)
if no_manual_depr_or_have_manual_depr_details_been_modified:
return True
return False
def validate_item(self):
item = frappe.get_cached_value(
"Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1
@@ -265,8 +223,10 @@ class Asset(AccountsController):
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
)
def make_depreciation_schedule(self, date_of_disposal):
if not self.get("schedules"):
def make_depreciation_schedule(self, date_of_sale):
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
"schedules"
):
self.schedules = []
if not self.available_for_use_date:
@@ -319,17 +279,17 @@ class Asset(AccountsController):
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_disposal:
if date_of_sale:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_disposal
finance_book, depreciation_amount, from_date, date_of_sale
)
if depreciation_amount > 0:
self.append(
"schedules",
{
"schedule_date": date_of_disposal,
"schedule_date": date_of_sale,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
@@ -404,9 +364,6 @@ class Asset(AccountsController):
},
)
if len(self.get("finance_books")) > 1 and any(start):
self.sort_depreciation_schedule()
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book
def clear_depreciation_schedule(self):
@@ -442,14 +399,6 @@ class Asset(AccountsController):
return start
def sort_depreciation_schedule(self):
self.schedules = sorted(
self.schedules, key=lambda s: (int(s.finance_book_id), getdate(s.schedule_date))
)
for idx, s in enumerate(self.schedules, 1):
s.idx = idx
def get_from_date(self, finance_book):
if not self.get("schedules"):
return self.available_for_use_date
@@ -582,9 +531,11 @@ class Asset(AccountsController):
return True
def set_accumulated_depreciation(
self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False
self, date_of_sale=None, date_of_return=None, ignore_booked_entry=False
):
straight_line_idx = []
straight_line_idx = [
d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
]
finance_books = []
for i, d in enumerate(self.get("schedules")):
@@ -592,16 +543,8 @@ class Asset(AccountsController):
continue
if int(d.finance_book_id) not in finance_books:
straight_line_idx = [
s.idx
for s in self.get("schedules")
if s.finance_book_id == d.finance_book_id
and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual")
]
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
)
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
finance_books.append(int(d.finance_book_id))
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
@@ -611,7 +554,7 @@ class Asset(AccountsController):
if (
straight_line_idx
and i == max(straight_line_idx) - 1
and not date_of_disposal
and not date_of_sale
and not date_of_return
):
book = self.get("finance_books")[cint(d.finance_book_id) - 1]
@@ -626,6 +569,9 @@ class Asset(AccountsController):
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
)
def get_value_after_depreciation(self, idx):
return flt(self.get("finance_books")[cint(idx) - 1].value_after_depreciation)
def validate_expected_value_after_useful_life(self):
for row in self.get("finance_books"):
accumulated_depreciation_after_full_schedule = [
@@ -680,20 +626,15 @@ class Asset(AccountsController):
movement.cancel()
def delete_depreciation_entries(self):
if self.calculate_depreciation:
for d in self.get("schedules"):
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
else:
depr_entries = self.get_manual_depreciation_entries()
for d in self.get("schedules"):
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
d.db_set("journal_entry", None)
for depr_entry in depr_entries or []:
frappe.get_doc("Journal Entry", depr_entry.name).cancel()
self.db_set(
"value_after_depreciation",
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
)
self.db_set(
"value_after_depreciation",
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
)
def set_status(self, status=None):
"""Get and update status"""
@@ -710,15 +651,11 @@ class Asset(AccountsController):
if self.journal_entry_for_scrap:
status = "Scrapped"
else:
expected_value_after_useful_life = 0
value_after_depreciation = self.value_after_depreciation
elif self.finance_books:
idx = self.get_default_finance_book_idx() or 0
if self.calculate_depreciation:
idx = self.get_default_finance_book_idx() or 0
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
value_after_depreciation = self.finance_books[idx].value_after_depreciation
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
value_after_depreciation = self.finance_books[idx].value_after_depreciation
if flt(value_after_depreciation) <= expected_value_after_useful_life:
status = "Fully Depreciated"
@@ -728,19 +665,6 @@ class Asset(AccountsController):
status = "Cancelled"
return status
def get_value_after_depreciation(self, finance_book=None):
if not self.calculate_depreciation:
return flt(self.value_after_depreciation, self.precision("gross_purchase_amount"))
if not finance_book:
return flt(
self.get("finance_books")[0].value_after_depreciation, self.precision("gross_purchase_amount")
)
for row in self.get("finance_books"):
if finance_book == row.finance_book:
return flt(row.value_after_depreciation, self.precision("gross_purchase_amount"))
def get_default_finance_book_idx(self):
if not self.get("default_finance_book") and self.company:
self.default_finance_book = erpnext.get_default_finance_book(self.company)
@@ -866,25 +790,6 @@ class Asset(AccountsController):
make_gl_entries(gl_entries)
self.db_set("booked_fixed_asset", 1)
@frappe.whitelist()
def get_manual_depreciation_entries(self):
(_, _, depreciation_expense_account) = get_depreciation_accounts(self)
gle = frappe.qb.DocType("GL Entry")
records = (
frappe.qb.from_(gle)
.select(gle.voucher_no.as_("name"), gle.debit.as_("value"), gle.posting_date)
.where(gle.against_voucher == self.name)
.where(gle.account == depreciation_expense_account)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.orderby(gle.posting_date)
.orderby(gle.creation)
).run(as_dict=True)
return records
@frappe.whitelist()
def get_depreciation_rate(self, args, on_validate=False):
if isinstance(args, string_types):
@@ -920,9 +825,7 @@ def update_maintenance_status():
for asset in assets:
asset = frappe.get_doc("Asset", asset.name)
if frappe.db.exists(
"Asset Repair", {"asset_name": asset.name, "repair_status": "Pending", "docstatus": 0}
):
if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}):
asset.set_status("Out of Order")
elif frappe.db.exists(
"Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}
@@ -933,6 +836,7 @@ def update_maintenance_status():
def make_post_gl_entry():
asset_categories = frappe.db.get_all("Asset Category", fields=["name", "enable_cwip_accounting"])
for asset_category in asset_categories:
@@ -1085,7 +989,7 @@ def make_journal_entry(asset_name):
depreciation_expense_account,
) = get_depreciation_accounts(asset)
depreciation_cost_center, depreciation_series = frappe.get_cached_value(
depreciation_cost_center, depreciation_series = frappe.db.get_value(
"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
@@ -1152,13 +1056,6 @@ def is_cwip_accounting_enabled(asset_category):
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
@frappe.whitelist()
def get_asset_value_after_depreciation(asset_name, finance_book=None):
asset = frappe.get_doc("Asset", asset_name)
return asset.get_value_after_depreciation(finance_book)
def get_total_days(date, frequency):
period_start_date = add_months(date, cint(frequency) * -1)
@@ -1168,6 +1065,12 @@ def get_total_days(date, frequency):
return date_diff(date, period_start_date)
def is_last_day_of_the_month(date):
last_day_of_the_month = get_last_day(date)
return getdate(last_day_of_the_month) == getdate(date)
@erpnext.allow_regional
def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):

View File

@@ -4,17 +4,7 @@
import frappe
from frappe import _
from frappe.utils import (
add_months,
cint,
flt,
get_last_day,
get_link_to_form,
getdate,
nowdate,
today,
)
from frappe.utils.user import get_users_with_role
from frappe.utils import cint, flt, getdate, today
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
@@ -30,22 +20,9 @@ def post_depreciation_entries(date=None):
if not date:
date = today()
failed_asset_names = []
for asset_name in get_depreciable_assets(date):
try:
make_depreciation_entry(asset_name, date)
frappe.db.commit()
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names)
frappe.db.commit()
for asset in get_depreciable_assets(date):
make_depreciation_entry(asset, date)
frappe.db.commit()
def get_depreciable_assets(date):
@@ -144,8 +121,6 @@ def make_depreciation_entry(asset_name, date=None):
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
asset.db_set("depr_entry_posting_status", "Successful")
asset.set_status()
return asset
@@ -209,42 +184,6 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
return credit_account, debit_account
def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
for asset_name in failed_asset_names:
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
def notify_depr_entry_posting_error(failed_asset_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
recipients = get_users_with_role("System Manager")
subject = _("Error while posting depreciation entries")
asset_links = get_comma_separated_asset_links(failed_asset_names)
message = (
_("Hi,")
+ "<br>"
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+ "."
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names):
asset_links = []
for asset_name in asset_names:
asset_links.append(get_link_to_form("Asset", asset_name))
asset_links = ", ".join(asset_links)
return asset_links
@frappe.whitelist()
def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
@@ -256,11 +195,6 @@ def scrap_asset(asset_name):
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
)
date = today()
depreciate_asset(asset, date)
asset.reload()
depreciation_series = frappe.get_cached_value(
"Company", asset.company, "series_for_depreciation_entry"
)
@@ -268,7 +202,7 @@ def scrap_asset(asset_name):
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Journal Entry"
je.naming_series = depreciation_series
je.posting_date = date
je.posting_date = today()
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
@@ -279,7 +213,7 @@ def scrap_asset(asset_name):
je.flags.ignore_permissions = True
je.submit()
frappe.db.set_value("Asset", asset_name, "disposal_date", date)
frappe.db.set_value("Asset", asset_name, "disposal_date", today())
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
asset.set_status("Scrapped")
@@ -290,9 +224,6 @@ def scrap_asset(asset_name):
def restore_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
reset_depreciation_schedule(asset, asset.disposal_date)
je = asset.journal_entry_for_scrap
asset.db_set("disposal_date", None)
@@ -303,99 +234,6 @@ def restore_asset(asset_name):
asset.set_status()
def depreciate_asset(asset, date):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_disposal=date)
asset.save()
make_depreciation_entry(asset.name, date)
def reset_depreciation_schedule(asset, date):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=date)
modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
def modify_depreciation_schedule_for_asset_repairs(asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_disposal(asset, date):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == date:
if not disposal_was_made_on_original_schedule_date(
asset, schedule, row, date
) or disposal_happens_in_the_future(date):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
idx = cint(schedule.finance_book_id)
asset.finance_books[idx - 1].value_after_depreciation += depreciation_amount
asset.save()
def get_depreciation_amount_in_je(journal_entry):
if journal_entry.accounts[0].debit_in_account_currency:
return journal_entry.accounts[0].debit_in_account_currency
else:
return journal_entry.accounts[0].credit_in_account_currency
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if is_last_day_of_the_month(finance_book.depreciation_start_date):
orginal_schedule_date = get_last_day(orginal_schedule_date)
if orginal_schedule_date == posting_date_of_disposal:
return True
return False
def disposal_happens_in_the_future(posting_date_of_disposal):
if posting_date_of_disposal > getdate():
return True
return False
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
(
fixed_asset_account,
@@ -469,8 +307,18 @@ def get_asset_details(asset, finance_book=None):
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
value_after_depreciation = asset.get_value_after_depreciation(finance_book)
idx = 1
if finance_book:
for d in asset.finance_books:
if d.finance_book == finance_book:
idx = d.idx
break
value_after_depreciation = (
asset.finance_books[idx - 1].value_after_depreciation
if asset.calculate_depreciation
else asset.value_after_depreciation
)
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
return (
@@ -510,9 +358,3 @@ def get_disposal_account_and_cost_center(company):
frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company))
return disposal_account, depreciation_cost_center
def is_last_day_of_the_month(date):
last_day_of_the_month = get_last_day(date)
return getdate(last_day_of_the_month) == getdate(date)

View File

@@ -4,22 +4,11 @@
import unittest
import frappe
from frappe.utils import (
add_days,
add_months,
cstr,
flt,
get_first_day,
get_last_day,
getdate,
nowdate,
)
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, update_maintenance_status
from erpnext.assets.doctype.asset.depreciation import (
is_last_day_of_the_month,
post_depreciation_entries,
restore_asset,
scrap_asset,
@@ -164,59 +153,28 @@ class TestAsset(AssetSetup):
self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_scrap_asset(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset(
calculate_depreciation=1,
available_for_use_date=purchase_date,
purchase_date=purchase_date,
available_for_use_date="2020-01-01",
purchase_date="2020-01-01",
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
post_depreciation_entries(date=add_months(purchase_date, 2))
asset.load_from_db()
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
self.assertEquals(accumulated_depr_amount, 18000.0)
post_depreciation_entries(date=add_months("2020-01-01", 4))
scrap_asset(asset.name)
asset.load_from_db()
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
self.assertEquals(
accumulated_depr_amount,
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
)
self.assertEqual(asset.status, "Scrapped")
self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = (
(
"_Test Accumulated Depreciations - _TC",
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
(
"_Test Gain/Loss on Asset Disposal - _TC",
flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
)
gle = frappe.db.sql(
@@ -225,7 +183,7 @@ class TestAsset(AssetSetup):
order by account""",
asset.journal_entry_for_scrap,
)
self.assertSequenceEqual(gle, expected_gle)
self.assertEqual(gle, expected_gle)
restore_asset(asset.name)
@@ -233,57 +191,34 @@ class TestAsset(AssetSetup):
self.assertFalse(asset.journal_entry_for_scrap)
self.assertEqual(asset.status, "Partially Depreciated")
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
this_month_depr_amount = 9000.0 if is_last_day_of_the_month(date) else 0
self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount)
def test_gle_made_by_asset_sale(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset(
calculate_depreciation=1,
available_for_use_date=purchase_date,
purchase_date=purchase_date,
available_for_use_date="2020-06-06",
purchase_date="2020-01-01",
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
total_number_of_depreciations=3,
frequency_of_depreciation=10,
depreciation_start_date="2020-12-31",
submit=1,
)
post_depreciation_entries(date=add_months(purchase_date, 2))
post_depreciation_entries(date="2021-01-01")
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
si.customer = "_Test Customer"
si.due_date = nowdate()
si.get("items")[0].rate = 25000
si.insert()
si.set_posting_time = 1
si.posting_date = "2021-10-31"
si.due_date = "2021-10-31"
si.get("items")[0].rate = 75000
si.submit()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
expected_gle = (
(
"_Test Accumulated Depreciations - _TC",
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
("_Test Accumulated Depreciations - _TC", 50490.2, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
(
"_Test Gain/Loss on Asset Disposal - _TC",
flt(57000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
("Debtors - _TC", 25000.0, 0.0),
("_Test Gain/Loss on Asset Disposal - _TC", 0.0, 25490.2),
("Debtors - _TC", 75000.0, 0.0),
)
gle = frappe.db.sql(
@@ -293,9 +228,14 @@ class TestAsset(AssetSetup):
si.name,
)
self.assertSequenceEqual(gle, expected_gle)
for i, gle_entry in enumerate(gle):
self.assertEqual(gle_entry[0], expected_gle[i][0])
self.assertEqual(flt(gle_entry[1], 1), flt(expected_gle[i][1], 1))
self.assertEqual(flt(gle_entry[2], 1), flt(expected_gle[i][2], 1))
si.load_from_db()
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_with_maintenance_required_status_after_sale(self):
@@ -1411,36 +1351,6 @@ class TestDepreciationBasics(AssetSetup):
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date))
def test_manual_depreciation_for_existing_asset(self):
asset = create_asset(
item_code="Macbook Pro",
is_existing_asset=1,
purchase_date="2020-01-30",
available_for_use_date="2020-01-30",
submit=1,
)
self.assertEqual(asset.status, "Submitted")
self.assertEqual(asset.get("value_after_depreciation"), 100000)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
)
for d in jv.accounts:
d.reference_type = "Asset"
d.reference_name = asset.name
jv.voucher_type = "Depreciation Entry"
jv.insert()
jv.submit()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 99900)
jv.cancel()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 100000)
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
@@ -1477,7 +1387,6 @@ def create_asset(**args):
"location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1,
"depr_entry_posting_status": args.depr_entry_posting_status or "",
}
)

View File

@@ -82,9 +82,6 @@ class AssetRepair(AccountsController):
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
def after_delete(self):
frappe.get_doc("Asset", self.asset).set_status()
def check_repair_status(self):
if self.repair_status == "Pending":
frappe.throw(_("Please update Repair Status."))

View File

@@ -6,7 +6,6 @@ import unittest
import frappe
from frappe.utils import flt, nowdate
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.test_asset import (
create_asset,
create_asset_data,
@@ -106,20 +105,20 @@ class TestAssetRepair(unittest.TestCase):
def test_increase_in_asset_value_due_to_stock_consumption(self):
asset = create_asset(calculate_depreciation=1, submit=1)
initial_asset_value = get_asset_value_after_depreciation(asset.name)
initial_asset_value = get_asset_value(asset)
asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1)
asset.reload()
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
asset = create_asset(calculate_depreciation=1, submit=1)
initial_asset_value = get_asset_value_after_depreciation(asset.name)
initial_asset_value = get_asset_value(asset)
asset_repair = create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
asset.reload()
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
def test_purchase_invoice(self):
@@ -144,6 +143,10 @@ class TestAssetRepair(unittest.TestCase):
)
def get_asset_value(asset):
return asset.finance_books[0].value_after_depreciation
def num_of_depreciations(asset):
return asset.finance_books[0].total_number_of_depreciations

View File

@@ -47,7 +47,7 @@ frappe.ui.form.on('Asset Value Adjustment', {
set_current_asset_value: function(frm) {
if (frm.doc.asset) {
frm.call({
method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation",
method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_current_asset_value",
args: {
asset: frm.doc.asset,
finance_book: frm.doc.finance_book

View File

@@ -10,10 +10,7 @@ from frappe.utils import cint, date_diff, flt, formatdate, getdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.assets.doctype.asset.asset import (
get_asset_value_after_depreciation,
get_depreciation_amount,
)
from erpnext.assets.doctype.asset.asset import get_depreciation_amount
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from erpnext.regional.india.utils import (
get_depreciation_amount as get_depreciation_amount_for_india,
@@ -48,7 +45,7 @@ class AssetValueAdjustment(Document):
def set_current_asset_value(self):
if not self.current_asset_value and self.asset:
self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book)
self.current_asset_value = get_current_asset_value(self.asset, self.finance_book)
def make_depreciation_entry(self):
asset = frappe.get_doc("Asset", self.asset)
@@ -151,3 +148,12 @@ class AssetValueAdjustment(Document):
for asset_data in asset.schedules:
if not asset_data.journal_entry:
asset_data.db_update()
@frappe.whitelist()
def get_current_asset_value(asset, finance_book=None):
cond = {"parent": asset, "parenttype": "Asset"}
if finance_book:
cond.update({"finance_book": finance_book})
return frappe.db.get_value("Asset Finance Book", cond, "value_after_depreciation")

View File

@@ -6,8 +6,10 @@ import unittest
import frappe
from frappe.utils import add_days, get_last_day, nowdate
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.test_asset import create_asset_data
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
get_current_asset_value,
)
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -41,7 +43,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
)
asset_doc.submit()
current_value = get_asset_value_after_depreciation(asset_doc.name)
current_value = get_current_asset_value(asset_doc.name)
self.assertEqual(current_value, 100000.0)
def test_asset_depreciation_value_adjustment(self):
@@ -71,7 +73,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
)
asset_doc.submit()
current_value = get_asset_value_after_depreciation(asset_doc.name)
current_value = get_current_asset_value(asset_doc.name)
adj_doc = make_asset_value_adjustment(
asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
)

View File

@@ -200,11 +200,11 @@ def get_children(doctype, parent=None, location=None, is_root=False):
name as value,
is_group as expandable
from
`tabLocation` comp
`tab{doctype}` comp
where
ifnull(parent_location, "")={parent}
""".format(
parent=frappe.db.escape(parent)
doctype=doctype, parent=frappe.db.escape(parent)
),
as_dict=1,
)

View File

@@ -4,7 +4,6 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.utils import cstr, flt, formatdate, getdate
from erpnext.accounts.report.financial_statements import (
@@ -12,8 +11,6 @@ from erpnext.accounts.report.financial_statements import (
get_period_list,
validate_fiscal_year,
)
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
def execute(filters=None):
@@ -88,9 +85,7 @@ def get_data(filters):
"asset_name",
"status",
"department",
"company",
"cost_center",
"calculate_depreciation",
"purchase_receipt",
"asset_category",
"purchase_date",
@@ -102,21 +97,12 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
for asset in assets_record:
if filters.finance_book:
if asset.asset_id not in assets_linked_to_fb:
continue
else:
if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
continue
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
asset_value = (
asset.gross_purchase_amount
- flt(asset.opening_accumulated_depreciation)
- flt(depreciation_amount_map.get(asset.name))
)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
@@ -127,7 +113,7 @@ def get_data(filters):
or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters),
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
@@ -151,7 +137,6 @@ def prepare_chart_data(data, filters):
filters.filter_based_on,
"Monthly",
company=filters.company,
ignore_fiscal_year=True,
)
for d in period_list:
@@ -185,15 +170,6 @@ def prepare_chart_data(data, filters):
}
def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
if asset.calculate_depreciation:
depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
else:
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
return flt(depr_amount, 2)
def get_finance_book_value_map(filters):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
@@ -213,31 +189,6 @@ def get_finance_book_value_map(filters):
)
def get_manual_depreciation_amount_of_asset(asset, filters):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
(_, _, depreciation_expense_account) = get_depreciation_accounts(asset)
gle = frappe.qb.DocType("GL Entry")
result = (
frappe.qb.from_(gle)
.select(Sum(gle.debit))
.where(gle.against_voucher == asset.asset_id)
.where(gle.account == depreciation_expense_account)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.where(gle.posting_date <= date)
).run()
if result and result[0] and result[0][0]:
depr_amount = result[0][0]
else:
depr_amount = 0
return depr_amount
def get_purchase_receipt_supplier_map():
return frappe._dict(
frappe.db.sql(

View File

@@ -43,11 +43,6 @@ frappe.ui.form.on("Purchase Order", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
// On cancel and amending a purchase order with advance payment, reset advance paid amount
if (frm.is_new()) {
frm.set_value("advance_paid", 0)
}
},
apply_tds: function(frm) {

View File

@@ -370,7 +370,7 @@
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Shipping Address",
"label": "Company Shipping Address",
"options": "Address",
"print_hide": 1
},
@@ -1118,8 +1118,7 @@
"fetch_from": "supplier.is_internal_supplier",
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
"read_only": 1
"label": "Is Internal Supplier"
},
{
"fetch_from": "supplier.represents_company",
@@ -1170,7 +1169,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2022-12-25 18:08:59.074182",
"modified": "2022-09-16 17:45:04.954055",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
from erpnext.accounts.party import get_party_account, get_party_account_currency
from erpnext.accounts.party import get_party_account_currency
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
from erpnext.controllers.buying_controller import BuyingController
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@@ -533,7 +533,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
target.set_advances()
target.set_payment_schedule()
target.credit_to = get_party_account("Supplier", source.supplier, source.company)
def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt)

View File

@@ -1239,11 +1239,6 @@ class TestPurchaseOrder(FrappeTestCase):
automatically_fetch_payment_terms(enable=0)
def test_variant_item_po(self):
po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1)
self.assertRaises(frappe.ValidationError, po.save)
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)
@@ -1347,8 +1342,8 @@ def create_purchase_order(**args):
},
)
po.set_missing_values()
if not args.do_not_save:
po.set_missing_values()
po.insert()
if not args.do_not_submit:
if po.is_subcontracted == "Yes":

View File

@@ -22,13 +22,6 @@ frappe.ui.form.on("Request for Quotation",{
}
};
}
frm.set_query('warehouse', 'items', () => ({
filters: {
company: frm.doc.company,
is_group: 0
}
}));
},
onload: function(frm) {

View File

@@ -479,7 +479,7 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt
conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date"))
rfq_data = frappe.db.sql(
f"""
"""
select
distinct rfq.name, rfq.transaction_date,
rfq.company
@@ -487,18 +487,15 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
where
rfq.name = rfq_supplier.parent
and rfq_supplier.supplier = %(supplier)s
and rfq_supplier.supplier = '{0}'
and rfq.docstatus = 1
and rfq.company = %(company)s
{conditions}
and rfq.company = '{1}'
{2}
order by rfq.transaction_date ASC
limit %(page_len)s offset %(start)s """,
{
"page_len": page_len,
"start": start,
"company": filters.get("company"),
"supplier": filters.get("supplier"),
},
limit %(page_len)s offset %(start)s """.format(
filters.get("supplier"), filters.get("company"), conditions
),
{"page_len": page_len, "start": start},
as_dict=1,
)

View File

@@ -152,7 +152,6 @@ class AccountsController(TransactionBase):
self.validate_inter_company_reference()
self.disable_pricing_rule_on_internal_transfer()
self.disable_tax_included_prices_for_internal_transfer()
self.set_incoming_rate()
if self.meta.get_field("currency"):
@@ -383,7 +382,7 @@ class AccountsController(TransactionBase):
self.get("inter_company_reference")
or self.get("inter_company_invoice_reference")
or self.get("inter_company_order_reference")
) and not self.get("is_return"):
):
msg = _("Internal Sale or Delivery Reference missing.")
msg += _("Please create purchase from internal sale or delivery document itself")
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
@@ -396,20 +395,6 @@ class AccountsController(TransactionBase):
alert=1,
)
def disable_tax_included_prices_for_internal_transfer(self):
if self.is_internal_transfer():
tax_updated = False
for tax in self.get("taxes"):
if tax.get("included_in_print_rate"):
tax.included_in_print_rate = 0
tax_updated = True
if tax_updated:
frappe.msgprint(
_("Disabled tax included prices since this {} is an internal transfer").format(self.doctype),
alert=1,
)
def validate_due_date(self):
if self.get("is_pos"):
return
@@ -573,12 +558,7 @@ class AccountsController(TransactionBase):
if bool(uom) != bool(stock_uom): # xor
item.stock_uom = item.uom = uom or stock_uom
# UOM cannot be zero so substitute as 1
item.conversion_factor = (
get_uom_conv_factor(item.get("uom"), item.get("stock_uom"))
or item.get("conversion_factor")
or 1
)
item.conversion_factor = get_uom_conv_factor(item.get("uom"), item.get("stock_uom"))
if self.doctype == "Purchase Invoice":
self.set_expense_account(for_validate)

View File

@@ -755,8 +755,6 @@ class BuyingController(StockController, Subcontracting):
asset.purchase_date = self.posting_date
asset.supplier = self.supplier
elif self.docstatus == 2:
if asset.docstatus == 2:
continue
if asset.docstatus == 0:
asset.set(field, None)
asset.supplier = None

View File

@@ -131,7 +131,7 @@ def validate_returned_items(doc):
)
elif ref.serial_no:
if d.qty and not d.serial_no:
if not d.serial_no:
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
else:
serial_nos = get_serial_nos(d.serial_no)
@@ -393,16 +393,6 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
if serial_nos:
target_doc.serial_no = "\n".join(serial_nos)
if source_doc.get("rejected_serial_no"):
returned_serial_nos = get_returned_serial_nos(
source_doc, source_parent, serial_no_field="rejected_serial_no"
)
rejected_serial_nos = list(
set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos)
)
if rejected_serial_nos:
target_doc.rejected_serial_no = "\n".join(rejected_serial_nos)
if doctype == "Purchase Receipt":
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype
@@ -597,7 +587,7 @@ def get_filters(
return filters
def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
def get_returned_serial_nos(child_doc, parent_doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
return_ref_field = frappe.scrub(child_doc.doctype)
@@ -606,7 +596,7 @@ def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
serial_nos = []
fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
filters = [
[parent_doc.doctype, "return_against", "=", parent_doc.name],
@@ -616,6 +606,6 @@ def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
]
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
serial_nos.extend(get_serial_nos(row.serial_no))
return serial_nos

View File

@@ -25,8 +25,8 @@ class SellingController(StockController):
def onload(self):
super(SellingController, self).onload()
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"):
for item in self.get("items") + (self.get("packed_items") or []):
item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True))
for item in self.get("items"):
item.update(get_bin_details(item.item_code, item.warehouse))
def validate(self):
super(SellingController, self).validate()
@@ -86,9 +86,6 @@ class SellingController(StockController):
)
if not self.meta.get_field("sales_team"):
party_details.pop("sales_team")
else:
self.set("sales_team", party_details.get("sales_team"))
self.update_if_missing(party_details)
elif lead:
@@ -442,17 +439,11 @@ class SellingController(StockController):
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
if d.doctype == "Packed Item":
incoming_rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("incoming_rate"),
)
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
if d.rate != rate:
d.rate = rate
frappe.msgprint(
@@ -581,7 +572,6 @@ class SellingController(StockController):
"customer_address": "address_display",
"shipping_address_name": "shipping_address",
"company_address": "company_address_display",
"dispatch_address_name": "dispatch_address",
}
for address_field, address_display_field in address_dict.items():

View File

@@ -58,7 +58,7 @@ status_map = {
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["Closed", "eval:self.status=='Closed'"],
["On Hold", "eval:self.status=='On Hold'"],
],
"Purchase Order": [
@@ -79,7 +79,7 @@ status_map = {
["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["Closed", "eval:self.status=='Closed'"],
],
"Delivery Note": [
["Draft", None],
@@ -87,7 +87,7 @@ status_map = {
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["Closed", "eval:self.status=='Closed'"],
],
"Purchase Receipt": [
["Draft", None],
@@ -95,7 +95,7 @@ status_map = {
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["Closed", "eval:self.status=='Closed'"],
],
"Material Request": [
["Draft", None],
@@ -333,21 +333,16 @@ class StatusUpdater(Document):
)
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
if qty_or_amount == "qty":
msg = _("Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.")
else:
msg = _("Overbilling of {0} {1} ignored for item {2} because you have {3} role.")
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
frappe.msgprint(
msg.format(
_(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]),
frappe.bold(item.get("item_code")),
role,
),
indicator="orange",
alert=True,
msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
action,
_(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]),
frappe.bold(item.get("item_code")),
role,
)
frappe.msgprint(msg, indicator="orange", alert=True)
def update_qty(self, update_modified=True):
"""Updates qty or amount at row level

View File

@@ -139,15 +139,13 @@ class StockController(AccountsController):
warehouse_with_no_account = []
precision = self.get_debit_field_precision()
for item_row in voucher_details:
sle_list = sle_map.get(item_row.name)
sle_rounding_diff = 0.0
if sle_list:
for sle in sle_list:
if warehouse_account.get(sle.warehouse):
# from warehouse account
sle_rounding_diff += flt(sle.stock_value_difference)
self.check_expense_account(item_row)
# expense account/ target_warehouse / source_warehouse
@@ -190,46 +188,6 @@ class StockController(AccountsController):
elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse)
if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer():
warehouse_asset_account = ""
if self.get("is_internal_customer"):
warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"]
elif self.get("is_internal_supplier"):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.db.get_value("Company", self.company, "default_expense_account")
gl_list.append(
self.get_gl_dict(
{
"account": expense_account,
"against": warehouse_asset_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"),
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
"debit": sle_rounding_diff,
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
},
warehouse_account[sle.warehouse]["account_currency"],
item=item_row,
)
)
gl_list.append(
self.get_gl_dict(
{
"account": warehouse_asset_account,
"against": expense_account,
"cost_center": item_row.cost_center,
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
"credit": sle_rounding_diff,
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
},
item=item_row,
)
)
if warehouse_with_no_account:
for wh in warehouse_with_no_account:
if frappe.db.get_value("Warehouse", wh, "company"):

View File

@@ -890,33 +890,24 @@ class calculate_taxes_and_totals(object):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def set_total_amount_to_default_mop(self, total_amount_to_pay):
total_paid_amount = 0
for payment in self.doc.get("payments"):
total_paid_amount += (
payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount
default_mode_of_payment = frappe.db.get_value(
"POS Payment Method",
{"parent": self.doc.pos_profile, "default": 1},
["mode_of_payment"],
as_dict=1,
)
if default_mode_of_payment:
self.doc.payments = []
self.doc.append(
"payments",
{
"mode_of_payment": default_mode_of_payment.mode_of_payment,
"amount": total_amount_to_pay,
"default": 1,
},
)
pending_amount = total_amount_to_pay - total_paid_amount
if pending_amount > 0:
default_mode_of_payment = frappe.db.get_value(
"POS Payment Method",
{"parent": self.doc.pos_profile, "default": 1},
["mode_of_payment"],
as_dict=1,
)
if default_mode_of_payment:
self.doc.payments = []
self.doc.append(
"payments",
{
"mode_of_payment": default_mode_of_payment.mode_of_payment,
"amount": pending_amount,
"default": 1,
},
)
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:

View File

@@ -102,7 +102,7 @@
}
],
"links": [],
"modified": "2022-12-28 16:35:34.377575",
"modified": "2021-06-29 18:27:02.832979",
"modified_by": "Administrator",
"module": "CRM",
"name": "Appointment",
@@ -121,6 +121,16 @@
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Guest",
"share": 1
},
{
"create": 1,
"delete": 1,

View File

@@ -5,9 +5,7 @@
from collections import Counter
import frappe
import frappe.share
from frappe import _
from frappe.desk.form.assign_to import add as add_assignment
from frappe.model.document import Document
from frappe.utils import get_url, getdate
from frappe.utils.verified_command import get_signed_params
@@ -120,18 +118,21 @@ class Appointment(Document):
self.party = lead.name
def auto_assign(self):
from frappe.desk.form.assign_to import add as add_assignemnt
existing_assignee = self.get_assignee_from_latest_opportunity()
if existing_assignee:
# If the latest opportunity is assigned to someone
# Assign the appointment to the same
self.assign_agent(existing_assignee)
add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [existing_assignee]})
return
if self._assign:
return
available_agents = _get_agents_sorted_by_asc_workload(getdate(self.scheduled_time))
for agent in available_agents:
if _check_agent_availability(agent, self.scheduled_time):
self.assign_agent(agent[0])
agent = agent[0]
add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [agent]})
break
def get_assignee_from_latest_opportunity(self):
@@ -186,15 +187,9 @@ class Appointment(Document):
params = {"email": self.customer_email, "appointment": self.name}
return get_url(verify_route + "?" + get_signed_params(params))
def assign_agent(self, agent):
if not frappe.has_permission(doc=self, user=agent):
frappe.share.add(self.doctype, self.name, agent, flags={"ignore_share_permission": True})
add_assignment({"doctype": self.doctype, "name": self.name, "assign_to": [agent]})
def _get_agents_sorted_by_asc_workload(date):
appointments = frappe.get_all("Appointment", fields="*")
appointments = frappe.db.get_list("Appointment", fields="*")
agent_list = _get_agent_list_as_strings()
if not appointments:
return agent_list
@@ -219,7 +214,7 @@ def _get_agent_list_as_strings():
def _check_agent_availability(agent_email, scheduled_time):
appointemnts_at_scheduled_time = frappe.get_all(
appointemnts_at_scheduled_time = frappe.get_list(
"Appointment", filters={"scheduled_time": scheduled_time}
)
for appointment in appointemnts_at_scheduled_time:

View File

@@ -1,5 +1,4 @@
{
"actions": [],
"creation": "2019-08-27 10:56:48.309824",
"doctype": "DocType",
"editable_grid": 1,
@@ -102,8 +101,7 @@
}
],
"issingle": 1,
"links": [],
"modified": "2022-12-28 16:41:28.773090",
"modified": "2019-11-26 12:14:17.669366",
"modified_by": "Administrator",
"module": "CRM",
"name": "Appointment Booking Settings",
@@ -119,6 +117,13 @@
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Guest",
"share": 1
},
{
"create": 1,
"email": 1,

View File

@@ -173,10 +173,7 @@ class TestWebsiteItem(unittest.TestCase):
# Website Item Portal Tests Begin
def test_website_item_breadcrumbs(self):
"""
Check if breadcrumbs include homepage, product listing navigation page,
parent item group(s) and item group
"""
"Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group."
from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
item_code = "Test Breadcrumb Item"
@@ -199,7 +196,7 @@ class TestWebsiteItem(unittest.TestCase):
breadcrumbs = get_parent_item_groups(item.item_group)
self.assertEqual(breadcrumbs[0]["name"], "Home")
self.assertEqual(breadcrumbs[1]["name"], "All Products")
self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")

View File

@@ -345,8 +345,7 @@
"image_field": "website_image",
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
"modified": "2022-09-13 04:05:11.614087",
"modified": "2022-06-28 17:10:30.613251",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Website Item",

View File

@@ -1345,7 +1345,7 @@ class QuickBooksMigrator(Document):
)[0]["name"]
def _publish(self, *args, **kwargs):
frappe.publish_realtime("quickbooks_progress_update", *args, **kwargs, user=self.modified_by)
frappe.publish_realtime("quickbooks_progress_update", *args, **kwargs)
def _get_unique_account_name(self, quickbooks_name, number=0):
if number:

View File

@@ -302,7 +302,6 @@ class TallyMigration(Document):
frappe.publish_realtime(
"tally_migration_progress_update",
{"title": title, "message": message, "count": count, "total": total},
user=self.modified_by,
)
def _import_master_data(self):

View File

@@ -302,7 +302,7 @@ def check_for_nexus(doc, tax_dict):
item.tax_collectable = flt(0)
item.taxable_amount = flt(0)
for tax in list(doc.taxes):
for tax in doc.taxes:
if tax.account_head == TAX_ACCOUNT_HEAD:
doc.taxes.remove(tax)
return

View File

@@ -334,6 +334,8 @@ has_website_permission = {
"Patient": "erpnext.healthcare.web_form.personal_details.personal_details.has_website_permission",
}
dump_report_map = "erpnext.startup.report_data_map.data_map"
before_tests = "erpnext.setup.utils.before_tests"
standard_queries = {

View File

@@ -5,16 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import (
add_days,
cint,
cstr,
formatdate,
get_datetime,
get_link_to_form,
getdate,
nowdate,
)
from frappe.utils import cint, cstr, formatdate, get_datetime, getdate, nowdate
from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee
@@ -115,6 +106,8 @@ class Attendance(Document):
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
def unlink_attendance_from_checkins(self):
from frappe.utils import get_link_to_form
EmployeeCheckin = frappe.qb.DocType("Employee Checkin")
linked_logs = (
frappe.qb.from_(EmployeeCheckin)
@@ -228,39 +221,75 @@ def mark_bulk_attendance(data):
attendance.submit()
def get_month_map():
return frappe._dict(
{
"January": 1,
"February": 2,
"March": 3,
"April": 4,
"May": 5,
"June": 6,
"July": 7,
"August": 8,
"September": 9,
"October": 10,
"November": 11,
"December": 12,
}
)
@frappe.whitelist()
def get_unmarked_days(employee, from_date, to_date, exclude_holidays=0):
def get_unmarked_days(employee, month, exclude_holidays=0):
import calendar
month_map = get_month_map()
today = get_datetime()
joining_date, relieving_date = frappe.get_cached_value(
"Employee", employee, ["date_of_joining", "relieving_date"]
)
start_day = 1
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
from_date = max(getdate(from_date), joining_date or getdate(from_date))
to_date = min(getdate(to_date), relieving_date or getdate(to_date))
if joining_date and joining_date.year == today.year and joining_date.month == month_map[month]:
start_day = joining_date.day
if (
relieving_date and relieving_date.year == today.year and relieving_date.month == month_map[month]
):
end_day = relieving_date.day + 1
dates_of_month = [
"{}-{}-{}".format(today.year, month_map[month], r) for r in range(start_day, end_day)
]
month_start, month_end = dates_of_month[0], dates_of_month[-1]
records = frappe.get_all(
"Attendance",
fields=["attendance_date", "employee"],
filters=[
["attendance_date", ">=", from_date],
["attendance_date", "<=", to_date],
["attendance_date", ">=", month_start],
["attendance_date", "<=", month_end],
["employee", "=", employee],
["docstatus", "!=", 2],
],
)
marked_days = [getdate(record.attendance_date) for record in records]
marked_days = [get_datetime(record.attendance_date) for record in records]
if cint(exclude_holidays):
holiday_dates = get_holiday_dates_for_employee(employee, from_date, to_date)
holidays = [getdate(record) for record in holiday_dates]
holiday_dates = get_holiday_dates_for_employee(employee, month_start, month_end)
holidays = [get_datetime(record) for record in holiday_dates]
marked_days.extend(holidays)
unmarked_days = []
while from_date <= to_date:
if from_date not in marked_days:
unmarked_days.append(from_date)
from_date = add_days(from_date, 1)
for date in dates_of_month:
date_time = get_datetime(date)
if today.day <= date_time.day and today.month <= date_time.month:
break
if date_time not in marked_days:
unmarked_days.append(date)
return unmarked_days

View File

@@ -1,6 +1,5 @@
frappe.listview_settings["Attendance"] = {
frappe.listview_settings['Attendance'] = {
add_fields: ["status", "attendance_date"],
get_indicator: function (doc) {
if (["Present", "Work From Home"].includes(doc.status)) {
return [__(doc.status), "green", "status,=," + doc.status];
@@ -11,185 +10,157 @@ frappe.listview_settings["Attendance"] = {
}
},
onload: function (list_view) {
onload: function(list_view) {
let me = this;
list_view.page.add_inner_button(__("Mark Attendance"), function () {
let first_day_of_month = moment().startOf('month');
if (moment().toDate().getDate() === 1) {
first_day_of_month = first_day_of_month.subtract(1, "month");
}
const months = moment.months();
const curMonth = moment().format("MMMM");
months.splice(months.indexOf(curMonth) + 1);
list_view.page.add_inner_button(__("Mark Attendance"), function() {
let dialog = new frappe.ui.Dialog({
title: __("Mark Attendance"),
fields: [
{
fieldname: "employee",
label: __("For Employee"),
fieldtype: "Link",
options: "Employee",
get_query: () => {
return {
query: "erpnext.controllers.queries.employee_query",
};
},
reqd: 1,
onchange: () => me.reset_dialog(dialog),
fields: [{
fieldname: 'employee',
label: __('For Employee'),
fieldtype: 'Link',
options: 'Employee',
get_query: () => {
return {query: "erpnext.controllers.queries.employee_query"};
},
{
fieldtype: "Section Break",
fieldname: "time_period_section",
hidden: 1,
},
{
label: __("Start"),
fieldtype: "Date",
fieldname: "from_date",
reqd: 1,
default: first_day_of_month.toDate(),
onchange: () => me.get_unmarked_days(dialog),
},
{
fieldtype: "Column Break",
fieldname: "time_period_column",
},
{
label: __("End"),
fieldtype: "Date",
fieldname: "to_date",
reqd: 1,
default: moment().subtract(1, 'days').toDate(),
onchange: () => me.get_unmarked_days(dialog),
},
{
fieldtype: "Section Break",
fieldname: "days_section",
hidden: 1,
},
{
label: __("Status"),
fieldtype: "Select",
fieldname: "status",
options: ["Present", "Absent", "Half Day", "Work From Home"],
reqd: 1,
},
{
label: __("Exclude Holidays"),
fieldtype: "Check",
fieldname: "exclude_holidays",
onchange: () => me.get_unmarked_days(dialog),
},
{
label: __("Unmarked Attendance for days"),
fieldname: "unmarked_days",
fieldtype: "MultiCheck",
options: [],
columns: 2,
},
],
reqd: 1,
onchange: function() {
dialog.set_df_property("unmarked_days", "hidden", 1);
dialog.set_df_property("status", "hidden", 1);
dialog.set_df_property("exclude_holidays", "hidden", 1);
dialog.set_df_property("month", "value", '');
dialog.set_df_property("unmarked_days", "options", []);
dialog.no_unmarked_days_left = false;
}
},
{
label: __("For Month"),
fieldtype: "Select",
fieldname: "month",
options: months,
reqd: 1,
onchange: function() {
if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
dialog.set_df_property("status", "hidden", 0);
dialog.set_df_property("exclude_holidays", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", []);
dialog.no_unmarked_days_left = false;
me.get_multi_select_options(
dialog.fields_dict.employee.value,
dialog.fields_dict.month.value,
dialog.fields_dict.exclude_holidays.get_value()
).then(options => {
if (options.length > 0) {
dialog.set_df_property("unmarked_days", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", options);
} else {
dialog.no_unmarked_days_left = true;
}
});
}
}
},
{
label: __("Status"),
fieldtype: "Select",
fieldname: "status",
options: ["Present", "Absent", "Half Day", "Work From Home"],
hidden: 1,
reqd: 1,
},
{
label: __("Exclude Holidays"),
fieldtype: "Check",
fieldname: "exclude_holidays",
hidden: 1,
onchange: function() {
if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
dialog.set_df_property("status", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", []);
dialog.no_unmarked_days_left = false;
me.get_multi_select_options(
dialog.fields_dict.employee.value,
dialog.fields_dict.month.value,
dialog.fields_dict.exclude_holidays.get_value()
).then(options => {
if (options.length > 0) {
dialog.set_df_property("unmarked_days", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", options);
} else {
dialog.no_unmarked_days_left = true;
}
});
}
}
},
{
label: __("Unmarked Attendance for days"),
fieldname: "unmarked_days",
fieldtype: "MultiCheck",
options: [],
columns: 2,
hidden: 1
}],
primary_action(data) {
if (cur_dialog.no_unmarked_days_left) {
frappe.msgprint(
__(
"Attendance from {0} to {1} has already been marked for the Employee {2}",
[data.from_date, data.to_date, data.employee]
)
);
frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}",
[dialog.fields_dict.month.value, dialog.fields_dict.employee.value]));
} else {
frappe.confirm(
__("Mark attendance as {0} for {1} on selected dates?", [
data.status,
data.employee,
]),
() => {
frappe.call({
method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance",
args: {
data: data,
},
callback: function (r) {
if (r.message === 1) {
frappe.show_alert({
message: __("Attendance Marked"),
indicator: "blue",
});
cur_dialog.hide();
}
},
});
}
);
frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status, data.month]), () => {
frappe.call({
method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance",
args: {
data: data
},
callback: function (r) {
if (r.message === 1) {
frappe.show_alert({
message: __("Attendance Marked"),
indicator: 'blue'
});
cur_dialog.hide();
}
}
});
});
}
dialog.hide();
list_view.refresh();
},
primary_action_label: __("Mark Attendance"),
primary_action_label: __('Mark Attendance')
});
dialog.show();
});
},
reset_dialog: function (dialog) {
let fields = dialog.fields_dict;
dialog.set_df_property(
"time_period_section",
"hidden",
fields.employee.value ? 0 : 1
);
dialog.set_df_property("days_section", "hidden", 1);
dialog.set_df_property("unmarked_days", "options", []);
dialog.no_unmarked_days_left = false;
fields.exclude_holidays.value = false;
fields.to_date.datepicker.update({
maxDate: moment().subtract(1, 'days').toDate()
get_multi_select_options: function(employee, month, exclude_holidays) {
return new Promise(resolve => {
frappe.call({
method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days',
async: false,
args: {
employee: employee,
month: month,
exclude_holidays: exclude_holidays
}
}).then(r => {
var options = [];
for (var d in r.message) {
var momentObj = moment(r.message[d], 'YYYY-MM-DD');
var date = momentObj.format('DD-MM-YYYY');
options.push({
"label": date,
"value": r.message[d],
"checked": 1
});
}
resolve(options);
});
});
this.get_unmarked_days(dialog)
},
get_unmarked_days: function (dialog) {
let fields = dialog.fields_dict;
if (fields.employee.value && fields.from_date.value && fields.to_date.value) {
dialog.set_df_property("days_section", "hidden", 0);
dialog.set_df_property("status", "hidden", 0);
dialog.set_df_property("exclude_holidays", "hidden", 0);
dialog.no_unmarked_days_left = false;
frappe
.call({
method: "erpnext.hr.doctype.attendance.attendance.get_unmarked_days",
async: false,
args: {
employee: fields.employee.value,
from_date: fields.from_date.value,
to_date: fields.to_date.value,
exclude_holidays: fields.exclude_holidays.value,
},
})
.then((r) => {
var options = [];
for (var d in r.message) {
var momentObj = moment(r.message[d], "YYYY-MM-DD");
var date = momentObj.format("DD-MM-YYYY");
options.push({
label: date,
value: r.message[d],
checked: 1,
});
}
dialog.set_df_property(
"unmarked_days",
"options",
options.length > 0 ? options : []
);
dialog.no_unmarked_days_left = options.length === 0;
});
}
},
}
};

Some files were not shown because too many files have changed in this diff Show More