mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-17 12:09:19 +00:00
Compare commits
192 Commits
copilot/de
...
v15.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5336cf08fa | ||
|
|
cb725dcf9f | ||
|
|
20e15ebd22 | ||
|
|
a699f8b9de | ||
|
|
c9674683d1 | ||
|
|
8276614c14 | ||
|
|
ed9cd7c92b | ||
|
|
d51237195a | ||
|
|
32039d4de1 | ||
|
|
ea43862fcd | ||
|
|
54a6fbaeba | ||
|
|
3ce734e372 | ||
|
|
d0a74419b3 | ||
|
|
57dea69185 | ||
|
|
cb4bb5b4ee | ||
|
|
376e09680c | ||
|
|
22b39ac4b4 | ||
|
|
ac61abb2e4 | ||
|
|
d18fd87650 | ||
|
|
00a62692dc | ||
|
|
0134be4915 | ||
|
|
f28d718732 | ||
|
|
df5fcbee71 | ||
|
|
76f3d4a31c | ||
|
|
0ec2978ea0 | ||
|
|
db60e147e0 | ||
|
|
33f1e709f1 | ||
|
|
696f8b0ae0 | ||
|
|
e68d2e138a | ||
|
|
461f7a1a1c | ||
|
|
6eaf67e700 | ||
|
|
f0a8c83fa8 | ||
|
|
feb49f23ed | ||
|
|
3be8bfe9d8 | ||
|
|
bf0d8c16ac | ||
|
|
9a9d5775e8 | ||
|
|
e2dd414793 | ||
|
|
8bc871a842 | ||
|
|
880cea5b36 | ||
|
|
28e6e5d910 | ||
|
|
77b1eedcf4 | ||
|
|
9f9a4a9eab | ||
|
|
a8749977e1 | ||
|
|
4fdd1ec498 | ||
|
|
6d325a40a1 | ||
|
|
011cf3d73e | ||
|
|
f6b56f225b | ||
|
|
d71d5a2981 | ||
|
|
6ab2f83a61 | ||
|
|
6f2b34e7d5 | ||
|
|
d3d10231b9 | ||
|
|
f61b476be1 | ||
|
|
0142a9308b | ||
|
|
fc09d757f0 | ||
|
|
cff56d4e50 | ||
|
|
d5bf7a039d | ||
|
|
02e225845e | ||
|
|
99b7cdb5be | ||
|
|
ddb1a7643a | ||
|
|
fb71a6e787 | ||
|
|
40cc3a7610 | ||
|
|
3c0f8b15a0 | ||
|
|
1b103faf05 | ||
|
|
54bed25056 | ||
|
|
b23fa1f5dd | ||
|
|
b1714ec21d | ||
|
|
848efe8047 | ||
|
|
24be04427c | ||
|
|
fff294fb37 | ||
|
|
2a5b3bdfd9 | ||
|
|
74983910bc | ||
|
|
6e463b1953 | ||
|
|
22e58e0ee0 | ||
|
|
ced6d004fb | ||
|
|
9781f9b7b1 | ||
|
|
46c86de093 | ||
|
|
a89af589e8 | ||
|
|
8602a3eab1 | ||
|
|
4eb80ea804 | ||
|
|
ef9e8406eb | ||
|
|
b3562bdb87 | ||
|
|
d74f0ef586 | ||
|
|
44bad3bd4a | ||
|
|
0e979b6c5b | ||
|
|
cbf8a1405a | ||
|
|
3d97a98fd7 | ||
|
|
3c843c7261 | ||
|
|
3f25614e54 | ||
|
|
463e71664c | ||
|
|
b48b858752 | ||
|
|
85d255c8a2 | ||
|
|
3abee937f9 | ||
|
|
37dbb4d3f9 | ||
|
|
1b83a91246 | ||
|
|
156d995ad8 | ||
|
|
c047fd7ac5 | ||
|
|
dc10a721ab | ||
|
|
59371358ae | ||
|
|
442c484258 | ||
|
|
fc3d303b82 | ||
|
|
915ca47515 | ||
|
|
a065f22a4c | ||
|
|
e156564ea4 | ||
|
|
7ad08179f2 | ||
|
|
df90fd7b35 | ||
|
|
21195343d5 | ||
|
|
61573f2645 | ||
|
|
463accbf04 | ||
|
|
fd7a768535 | ||
|
|
e71ef10ca9 | ||
|
|
7d0f1f4235 | ||
|
|
e0b0b6bb7d | ||
|
|
65f23485d3 | ||
|
|
5171e3238d | ||
|
|
fc4bcc0965 | ||
|
|
febd20acbc | ||
|
|
be8399f52e | ||
|
|
c29e22b3d1 | ||
|
|
796b1aa694 | ||
|
|
8d66848f9d | ||
|
|
9dae84feba | ||
|
|
1e218c12a0 | ||
|
|
847dd9e671 | ||
|
|
b1982a6961 | ||
|
|
e52291506e | ||
|
|
618a9ee49b | ||
|
|
71361f7673 | ||
|
|
b96be67a1f | ||
|
|
7bc02c49ba | ||
|
|
7524e425da | ||
|
|
39a178d27a | ||
|
|
fad8228a67 | ||
|
|
8ef48bc6b7 | ||
|
|
0ab63f91f8 | ||
|
|
e9bf48df9c | ||
|
|
c8791108de | ||
|
|
964a7a3dbd | ||
|
|
c8c8c6533b | ||
|
|
e51e5b36e2 | ||
|
|
80dddb40ae | ||
|
|
6df125a05f | ||
|
|
06bb1a3208 | ||
|
|
aa19055899 | ||
|
|
7abe5d9905 | ||
|
|
2ba5bb8abc | ||
|
|
71538cfab1 | ||
|
|
9675da6f38 | ||
|
|
95d6742587 | ||
|
|
98a8267c38 | ||
|
|
7a5bfe0009 | ||
|
|
c8243ec8e5 | ||
|
|
a72988a514 | ||
|
|
0589232d3b | ||
|
|
59e67cd384 | ||
|
|
254ec2cfd1 | ||
|
|
9aa29f55d9 | ||
|
|
8b3c4a948c | ||
|
|
98a7c170a0 | ||
|
|
e7423109b6 | ||
|
|
4f9210541e | ||
|
|
11dd1f14ff | ||
|
|
9ce123d0b9 | ||
|
|
f64fdb6870 | ||
|
|
f132552968 | ||
|
|
18e40dd032 | ||
|
|
4819fde8c5 | ||
|
|
5c46d7452e | ||
|
|
4034c16cde | ||
|
|
b03c65f21d | ||
|
|
a3d3c0024e | ||
|
|
2149de44b1 | ||
|
|
f382b1cf61 | ||
|
|
bfd240a19d | ||
|
|
08ea62f4e4 | ||
|
|
40443258cf | ||
|
|
2f5d991225 | ||
|
|
a871d955d4 | ||
|
|
882bd8e93a | ||
|
|
da5bf501eb | ||
|
|
31557902b8 | ||
|
|
bdb369e2b4 | ||
|
|
0925cb28c7 | ||
|
|
9863ba5fd8 | ||
|
|
889f84bcb7 | ||
|
|
b9e4719045 | ||
|
|
5cca001a58 | ||
|
|
e76860fae1 | ||
|
|
844e6f47df | ||
|
|
62d9de4848 | ||
|
|
fa5c75fd0a | ||
|
|
777c1dd1ea | ||
|
|
fca812448e |
60
.github/helper/translation.py
vendored
60
.github/helper/translation.py
vendored
@@ -1,60 +0,0 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
errors_encounter = 0
|
||||
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
|
||||
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
|
||||
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
|
||||
f_string_pattern = re.compile(r"_\(f[\"']")
|
||||
starts_with_f_pattern = re.compile(r"_\(f")
|
||||
|
||||
# skip first argument
|
||||
files = sys.argv[1:]
|
||||
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
|
||||
|
||||
for _file in files_to_scan:
|
||||
with open(_file, 'r') as f:
|
||||
print(f'Checking: {_file}')
|
||||
file_lines = f.readlines()
|
||||
for line_number, line in enumerate(file_lines, 1):
|
||||
if 'frappe-lint: disable-translate' in line:
|
||||
continue
|
||||
|
||||
start_matches = start_pattern.search(line)
|
||||
if start_matches:
|
||||
starts_with_f = starts_with_f_pattern.search(line)
|
||||
|
||||
if starts_with_f:
|
||||
has_f_string = f_string_pattern.search(line)
|
||||
if has_f_string:
|
||||
errors_encounter += 1
|
||||
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
match = pattern.search(line)
|
||||
error_found = False
|
||||
|
||||
if not match and line.endswith((',\n', '[\n')):
|
||||
# concat remaining text to validate multiline pattern
|
||||
line = "".join(file_lines[line_number - 1:])
|
||||
line = line[start_matches.start() + 1:]
|
||||
match = pattern.match(line)
|
||||
|
||||
if not match:
|
||||
error_found = True
|
||||
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')
|
||||
|
||||
if not error_found and not words_pattern.search(line):
|
||||
error_found = True
|
||||
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')
|
||||
|
||||
if error_found:
|
||||
errors_encounter += 1
|
||||
|
||||
if errors_encounter > 0:
|
||||
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('\nGood To Go!')
|
||||
26
.github/workflows/backport.yml
vendored
26
.github/workflows/backport.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: "frappe/backport"
|
||||
path: ./actions
|
||||
ref: develop
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run backport
|
||||
uses: ./actions/backport
|
||||
with:
|
||||
token: ${{secrets.BACKPORT_BOT_TOKEN}}
|
||||
labelsToAdd: "backport"
|
||||
title: "{{originalTitle}}"
|
||||
32
.github/workflows/initiate_release.yml
vendored
32
.github/workflows/initiate_release.yml
vendored
@@ -1,32 +0,0 @@
|
||||
# This workflow is agnostic to branches. Only maintain on develop branch.
|
||||
# To add/remove versions just modify the matrix.
|
||||
|
||||
name: Create weekly release pull requests
|
||||
on:
|
||||
schedule:
|
||||
# 9:30 UTC => 3 PM IST Tuesday
|
||||
- cron: "30 9 * * 2"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stable-release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ["13", "14"]
|
||||
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
with:
|
||||
route: POST /repos/{owner}/{repo}/pulls
|
||||
owner: frappe
|
||||
repo: erpnext
|
||||
title: |-
|
||||
"chore: release v${{ matrix.version }}"
|
||||
body: "Automated weekly release."
|
||||
base: version-${{ matrix.version }}
|
||||
head: version-${{ matrix.version }}-hotfix
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -2,21 +2,23 @@ name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-13
|
||||
- version-15
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Entire Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||
|
||||
26
.github/workflows/server-tests-mariadb.yml
vendored
26
.github/workflows/server-tests-mariadb.yml
vendored
@@ -117,7 +117,7 @@ jobs:
|
||||
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
|
||||
|
||||
- name: Run Tests
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --with-coverage --total-builds 4 --build-number ${{ matrix.container }}'
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 4 --build-number ${{ matrix.container }}'
|
||||
env:
|
||||
TYPE: server
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
@@ -126,27 +126,3 @@ jobs:
|
||||
- name: Show bench output
|
||||
if: ${{ always() }}
|
||||
run: cat ~/frappe-bench/bench_start.log || true
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-${{ matrix.container }}
|
||||
path: /home/runner/frappe-bench/sites/coverage.xml
|
||||
|
||||
coverage:
|
||||
name: Coverage Wrap Up
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
name: MariaDB
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"branches": ["version-13"],
|
||||
"branches": ["version-15"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
@@ -21,4 +21,4 @@
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "15.0.0-dev"
|
||||
__version__ = "15.2.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -89,7 +89,6 @@ class BankTransaction(StatusUpdater):
|
||||
- 0 > a: Error: already over-allocated
|
||||
- clear means: set the latest transaction date as clearance date
|
||||
"""
|
||||
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
|
||||
remaining_amount = self.unallocated_amount
|
||||
for payment_entry in self.payment_entries:
|
||||
if payment_entry.allocated_amount == 0.0:
|
||||
|
||||
@@ -53,10 +53,18 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
of Accounts. Please enter the account names and add more rows as per your requirement.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label : "Company",
|
||||
fieldname: "company",
|
||||
fieldtype: "Link",
|
||||
reqd: 1,
|
||||
hidden: 1,
|
||||
default: frm.doc.company,
|
||||
},
|
||||
],
|
||||
primary_action: function() {
|
||||
var data = d.get_values();
|
||||
let data = d.get_values();
|
||||
|
||||
if (!data.template_type) {
|
||||
frappe.throw(__('Please select <b>Template Type</b> to download template'));
|
||||
@@ -66,7 +74,8 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
'/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
|
||||
{
|
||||
file_type: data.file_type,
|
||||
template_type: data.template_type
|
||||
template_type: data.template_type,
|
||||
company: data.company
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from functools import reduce
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form.linked_with import get_linked_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, cstr
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
@@ -294,10 +295,8 @@ def build_response_as_excel(writer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_template(file_type, template_type):
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
|
||||
writer = get_template(template_type)
|
||||
def download_template(file_type, template_type, company):
|
||||
writer = get_template(template_type, company)
|
||||
|
||||
if file_type == "CSV":
|
||||
# download csv file
|
||||
@@ -308,8 +307,7 @@ def download_template(file_type, template_type):
|
||||
build_response_as_excel(writer)
|
||||
|
||||
|
||||
def get_template(template_type):
|
||||
|
||||
def get_template(template_type, company):
|
||||
fields = [
|
||||
"Account Name",
|
||||
"Parent Account",
|
||||
@@ -335,34 +333,17 @@ def get_template(template_type):
|
||||
["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
|
||||
)
|
||||
else:
|
||||
writer = get_sample_template(writer)
|
||||
writer = get_sample_template(writer, company)
|
||||
|
||||
return writer
|
||||
|
||||
|
||||
def get_sample_template(writer):
|
||||
template = [
|
||||
["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"],
|
||||
["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"],
|
||||
["Equity", "", "", "", 1, "", "Equity"],
|
||||
["Expenses", "", "", "", 1, "", "Expense"],
|
||||
["Income", "", "", "", 1, "", "Income"],
|
||||
["Bank Accounts", "Application Of Funds(Assets)", "", "", 1, "Bank", "Asset"],
|
||||
["Cash In Hand", "Application Of Funds(Assets)", "", "", 1, "Cash", "Asset"],
|
||||
["Stock Assets", "Application Of Funds(Assets)", "", "", 1, "Stock", "Asset"],
|
||||
["Cost Of Goods Sold", "Expenses", "", "", 0, "Cost of Goods Sold", "Expense"],
|
||||
["Asset Depreciation", "Expenses", "", "", 0, "Depreciation", "Expense"],
|
||||
["Fixed Assets", "Application Of Funds(Assets)", "", "", 0, "Fixed Asset", "Asset"],
|
||||
["Accounts Payable", "Sources Of Funds(Liabilities)", "", "", 0, "Payable", "Liability"],
|
||||
["Accounts Receivable", "Application Of Funds(Assets)", "", "", 1, "Receivable", "Asset"],
|
||||
["Stock Expenses", "Expenses", "", "", 0, "Stock Adjustment", "Expense"],
|
||||
["Sample Bank", "Bank Accounts", "", "", 0, "Bank", "Asset"],
|
||||
["Cash", "Cash In Hand", "", "", 0, "Cash", "Asset"],
|
||||
["Stores", "Stock Assets", "", "", 0, "Stock", "Asset"],
|
||||
]
|
||||
|
||||
for row in template:
|
||||
writer.writerow(row)
|
||||
def get_sample_template(writer, company):
|
||||
currency = frappe.db.get_value("Company", company, "default_currency")
|
||||
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f:
|
||||
for row in f:
|
||||
row = row.strip().split(",") + [currency]
|
||||
writer.writerow(row)
|
||||
|
||||
return writer
|
||||
|
||||
@@ -453,14 +434,11 @@ def get_mandatory_account_types():
|
||||
|
||||
|
||||
def unset_existing_data(company):
|
||||
linked = frappe.db.sql(
|
||||
'''select fieldname from tabDocField
|
||||
where fieldtype="Link" and options="Account" and parent="Company"''',
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
# remove accounts data from company
|
||||
update_values = {d.fieldname: "" for d in linked}
|
||||
|
||||
fieldnames = get_linked_fields("Account").get("Company", {}).get("fieldname", [])
|
||||
linked = [{"fieldname": name} for name in fieldnames]
|
||||
update_values = {d.get("fieldname"): "" for d in linked}
|
||||
frappe.db.set_value("Company", company, update_values, update_values)
|
||||
|
||||
# remove accounts data from various doctypes
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
Application Of Funds(Assets),,,,1,,Asset
|
||||
Sources Of Funds(Liabilities),,,,1,,Liability
|
||||
Equity,,,,1,,Equity
|
||||
Expenses,,,,1,Expense Account,Expense
|
||||
Income,,,,1,Income Account,Income
|
||||
Bank Accounts,Application Of Funds(Assets),,,1,Bank,Asset
|
||||
Cash In Hand,Application Of Funds(Assets),,,1,Cash,Asset
|
||||
Stock Assets,Application Of Funds(Assets),,,1,Stock,Asset
|
||||
Cost Of Goods Sold,Expenses,,,0,Cost of Goods Sold,Expense
|
||||
Asset Depreciation,Expenses,,,0,Depreciation,Expense
|
||||
Fixed Assets,Application Of Funds(Assets),,,0,Fixed Asset,Asset
|
||||
Accounts Payable,Sources Of Funds(Liabilities),,,0,Payable,Liability
|
||||
Accounts Receivable,Application Of Funds(Assets),,,1,Receivable,Asset
|
||||
Stock Expenses,Expenses,,,0,Stock Adjustment,Expense
|
||||
Sample Bank,Bank Accounts,,,0,Bank,Asset
|
||||
Cash,Cash In Hand,,,0,Cash,Asset
|
||||
Stores,Stock Assets,,,0,Stock,Asset
|
||||
|
@@ -154,7 +154,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
frm.events.show_general_ledger(frm);
|
||||
erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm);
|
||||
if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) {
|
||||
if((frm.doc.references) && (frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0}))) {
|
||||
frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() {
|
||||
frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
|
||||
}, __('Actions'));
|
||||
|
||||
@@ -595,6 +595,7 @@
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "\nDraft\nSubmitted\nCancelled",
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -750,7 +751,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-23 18:07:38.023010",
|
||||
"modified": "2023-11-08 21:51:03.482709",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -33,6 +33,7 @@ from erpnext.accounts.utils import (
|
||||
get_account_currency,
|
||||
get_balance_on,
|
||||
get_outstanding_invoices,
|
||||
get_party_types_from_account_type,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import (
|
||||
AccountsController,
|
||||
@@ -83,7 +84,6 @@ class PaymentEntry(AccountsController):
|
||||
self.apply_taxes()
|
||||
self.set_amounts_after_tax()
|
||||
self.clear_unallocated_reference_document_rows()
|
||||
self.validate_payment_against_negative_invoice()
|
||||
self.validate_transaction_reference()
|
||||
self.set_title()
|
||||
self.set_remarks()
|
||||
@@ -952,35 +952,6 @@ class PaymentEntry(AccountsController):
|
||||
self.name,
|
||||
)
|
||||
|
||||
def validate_payment_against_negative_invoice(self):
|
||||
if (self.payment_type != "Pay" or self.party_type != "Customer") and (
|
||||
self.payment_type != "Receive" or self.party_type != "Supplier"
|
||||
):
|
||||
return
|
||||
|
||||
total_negative_outstanding = sum(
|
||||
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
||||
)
|
||||
|
||||
paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
|
||||
additional_charges = sum(flt(d.amount) for d in self.deductions)
|
||||
|
||||
if not total_negative_outstanding:
|
||||
if self.party_type == "Customer":
|
||||
msg = _("Cannot pay to Customer without any negative outstanding invoice")
|
||||
else:
|
||||
msg = _("Cannot receive from Supplier without any negative outstanding invoice")
|
||||
|
||||
frappe.throw(msg, InvalidPaymentEntry)
|
||||
|
||||
elif paid_amount - additional_charges > total_negative_outstanding:
|
||||
frappe.throw(
|
||||
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
|
||||
fmt_money(total_negative_outstanding)
|
||||
),
|
||||
InvalidPaymentEntry,
|
||||
)
|
||||
|
||||
def set_title(self):
|
||||
if frappe.flags.in_import and self.title:
|
||||
# do not set title dynamically if title exists during data import.
|
||||
@@ -1051,6 +1022,7 @@ class PaymentEntry(AccountsController):
|
||||
self.add_bank_gl_entries(gl_entries)
|
||||
self.add_deductions_gl_entries(gl_entries)
|
||||
self.add_tax_gl_entries(gl_entries)
|
||||
add_regional_gl_entries(gl_entries, self)
|
||||
return gl_entries
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
@@ -1083,11 +1055,9 @@ class PaymentEntry(AccountsController):
|
||||
item=self,
|
||||
)
|
||||
|
||||
dr_or_cr = (
|
||||
"credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit"
|
||||
)
|
||||
|
||||
for d in self.get("references"):
|
||||
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
|
||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||
cost_center = self.cost_center
|
||||
if d.reference_doctype == "Sales Invoice" and not cost_center:
|
||||
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
|
||||
@@ -1103,10 +1073,25 @@ class PaymentEntry(AccountsController):
|
||||
against_voucher_type = d.reference_doctype
|
||||
against_voucher = d.reference_name
|
||||
|
||||
reverse_dr_or_cr = 0
|
||||
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
|
||||
payable_party_types = get_party_types_from_account_type("Payable")
|
||||
receivable_party_types = get_party_types_from_account_type("Receivable")
|
||||
if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"):
|
||||
reverse_dr_or_cr = 1
|
||||
elif (
|
||||
is_return and self.party_type in payable_party_types and (self.payment_type == "Receive")
|
||||
):
|
||||
reverse_dr_or_cr = 1
|
||||
|
||||
if is_return and not reverse_dr_or_cr:
|
||||
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
||||
|
||||
gle.update(
|
||||
{
|
||||
dr_or_cr: allocated_amount_in_company_currency,
|
||||
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
||||
dr_or_cr: abs(allocated_amount_in_company_currency),
|
||||
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
|
||||
"against_voucher_type": against_voucher_type,
|
||||
"against_voucher": against_voucher,
|
||||
"cost_center": cost_center,
|
||||
@@ -1114,6 +1099,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
gl_entries.append(gle)
|
||||
|
||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||
if self.unallocated_amount:
|
||||
exchange_rate = self.get_exchange_rate()
|
||||
base_unallocated_amount = self.unallocated_amount * exchange_rate
|
||||
@@ -2625,3 +2611,8 @@ def make_payment_order(source_name, target_doc=None):
|
||||
)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def add_regional_gl_entries(gl_entries, doc):
|
||||
return
|
||||
|
||||
@@ -683,17 +683,6 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
self.validate_gl_entries(pe.name, expected_gle)
|
||||
|
||||
def test_payment_against_negative_sales_invoice(self):
|
||||
pe1 = frappe.new_doc("Payment Entry")
|
||||
pe1.payment_type = "Pay"
|
||||
pe1.company = "_Test Company"
|
||||
pe1.party_type = "Customer"
|
||||
pe1.party = "_Test Customer"
|
||||
pe1.paid_from = "_Test Cash - _TC"
|
||||
pe1.paid_amount = 100
|
||||
pe1.received_amount = 100
|
||||
|
||||
self.assertRaises(InvalidPaymentEntry, pe1.validate)
|
||||
|
||||
si1 = create_sales_invoice()
|
||||
|
||||
# create full payment entry against si1
|
||||
@@ -751,8 +740,6 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
|
||||
# pay more than outstanding against si1
|
||||
pe3 = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Cash - _TC")
|
||||
pe3.paid_amount = pe3.received_amount = 300
|
||||
self.assertRaises(InvalidPaymentEntry, pe3.validate)
|
||||
|
||||
# pay negative outstanding against si1
|
||||
pe3.paid_to = "Debtors - _TC"
|
||||
@@ -1262,6 +1249,91 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, so.rounded_total)
|
||||
|
||||
def test_receive_payment_from_payable_party_type(self):
|
||||
"""
|
||||
Checks GL entries generated while receiving payments from a Payable Party Type.
|
||||
"""
|
||||
pe = create_payment_entry(
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
payment_type="Receive",
|
||||
paid_from="Creditors - _TC",
|
||||
paid_to="_Test Cash - _TC",
|
||||
save=True,
|
||||
submit=True,
|
||||
)
|
||||
self.voucher_no = pe.name
|
||||
self.expected_gle = [
|
||||
{"account": "Creditors - _TC", "debit": 0.0, "credit": 1000.0},
|
||||
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||
]
|
||||
self.check_gl_entries()
|
||||
|
||||
def test_payment_against_partial_return_invoice(self):
|
||||
"""
|
||||
Checks GL entries generated for partial return invoice payments.
|
||||
"""
|
||||
si = create_sales_invoice(qty=10, rate=10, customer="_Test Customer")
|
||||
credit_note = create_sales_invoice(
|
||||
qty=-4, rate=10, customer="_Test Customer", is_return=1, return_against=si.name
|
||||
)
|
||||
pe = create_payment_entry(
|
||||
party_type="Customer",
|
||||
party="_Test Customer",
|
||||
payment_type="Receive",
|
||||
paid_from="Debtors - _TC",
|
||||
paid_to="_Test Cash - _TC",
|
||||
)
|
||||
pe.set(
|
||||
"references",
|
||||
[
|
||||
{
|
||||
"reference_doctype": "Sales Invoice",
|
||||
"reference_name": si.name,
|
||||
"due_date": si.get("due_date"),
|
||||
"total_amount": si.grand_total,
|
||||
"outstanding_amount": si.outstanding_amount,
|
||||
"allocated_amount": si.outstanding_amount,
|
||||
},
|
||||
{
|
||||
"reference_doctype": "Sales Invoice",
|
||||
"reference_name": credit_note.name,
|
||||
"due_date": credit_note.get("due_date"),
|
||||
"total_amount": credit_note.grand_total,
|
||||
"outstanding_amount": credit_note.outstanding_amount,
|
||||
"allocated_amount": credit_note.outstanding_amount,
|
||||
},
|
||||
],
|
||||
)
|
||||
pe.save()
|
||||
pe.submit()
|
||||
self.assertEqual(pe.total_allocated_amount, 60)
|
||||
self.assertEqual(pe.unallocated_amount, 940)
|
||||
self.voucher_no = pe.name
|
||||
self.expected_gle = [
|
||||
{"account": "Debtors - _TC", "debit": 40.0, "credit": 0.0},
|
||||
{"account": "Debtors - _TC", "debit": 0.0, "credit": 940.0},
|
||||
{"account": "Debtors - _TC", "debit": 0.0, "credit": 100.0},
|
||||
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||
]
|
||||
self.check_gl_entries()
|
||||
|
||||
def check_gl_entries(self):
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
gl_entries = (
|
||||
frappe.qb.from_(gle)
|
||||
.select(
|
||||
gle.account,
|
||||
gle.debit,
|
||||
gle.credit,
|
||||
)
|
||||
.where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0))
|
||||
.orderby(gle.account, gle.debit, gle.credit, order=frappe.qb.desc)
|
||||
).run(as_dict=True)
|
||||
for row in range(len(self.expected_gle)):
|
||||
for field in ["account", "debit", "credit"]:
|
||||
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Posting Date"
|
||||
"label": "Posting Date",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "account_type",
|
||||
@@ -64,7 +65,8 @@
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Voucher Type",
|
||||
"options": "DocType"
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
@@ -72,14 +74,16 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Voucher No",
|
||||
"options": "voucher_type"
|
||||
"options": "voucher_type",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Against Voucher Type",
|
||||
"options": "DocType"
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher_no",
|
||||
@@ -87,7 +91,8 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Against Voucher No",
|
||||
"options": "against_voucher_type"
|
||||
"options": "against_voucher_type",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
@@ -147,13 +152,14 @@
|
||||
{
|
||||
"fieldname": "voucher_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Detail No"
|
||||
"label": "Voucher Detail No",
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-29 12:24:20.500632",
|
||||
"modified": "2023-11-03 16:39:58.904113",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Ledger Entry",
|
||||
|
||||
@@ -109,6 +109,8 @@ class PaymentReconciliation(Document):
|
||||
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
|
||||
)
|
||||
|
||||
limit = f"limit {self.payment_limit}" if self.payment_limit else " "
|
||||
|
||||
# nosemgrep
|
||||
journal_entries = frappe.db.sql(
|
||||
"""
|
||||
@@ -132,11 +134,13 @@ class PaymentReconciliation(Document):
|
||||
ELSE {bank_account_condition}
|
||||
END)
|
||||
order by t1.posting_date
|
||||
{limit}
|
||||
""".format(
|
||||
**{
|
||||
"dr_or_cr": dr_or_cr,
|
||||
"bank_account_condition": bank_account_condition,
|
||||
"condition": condition,
|
||||
"limit": limit,
|
||||
}
|
||||
),
|
||||
{
|
||||
@@ -162,7 +166,7 @@ class PaymentReconciliation(Document):
|
||||
if self.payment_name:
|
||||
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
||||
|
||||
self.return_invoices = (
|
||||
self.return_invoices_query = (
|
||||
qb.from_(doc)
|
||||
.select(
|
||||
ConstantColumn(voucher_type).as_("voucher_type"),
|
||||
@@ -170,8 +174,11 @@ class PaymentReconciliation(Document):
|
||||
doc.return_against,
|
||||
)
|
||||
.where(Criterion.all(conditions))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
if self.payment_limit:
|
||||
self.return_invoices_query = self.return_invoices_query.limit(self.payment_limit)
|
||||
|
||||
self.return_invoices = self.return_invoices_query.run(as_dict=True)
|
||||
|
||||
def get_dr_or_cr_notes(self):
|
||||
|
||||
|
||||
@@ -776,19 +776,28 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
)
|
||||
|
||||
create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02")
|
||||
make_stock_entry(
|
||||
se = make_stock_entry(
|
||||
target="_Test Warehouse - _TC",
|
||||
item_code="_BATCH ITEM Test For Reserve",
|
||||
qty=20,
|
||||
qty=30,
|
||||
basic_rate=100,
|
||||
batch_no="TestBatch-RS 02",
|
||||
)
|
||||
|
||||
se.reload()
|
||||
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
# POS Invoice 1, for the batch without bundle
|
||||
pos_inv1 = create_pos_invoice(
|
||||
item="_BATCH ITEM Test For Reserve", rate=300, qty=15, batch_no="TestBatch-RS 02"
|
||||
item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1
|
||||
)
|
||||
|
||||
pos_inv1.items[0].batch_no = batch_no
|
||||
pos_inv1.save()
|
||||
pos_inv1.submit()
|
||||
pos_inv1.reload()
|
||||
|
||||
self.assertFalse(pos_inv1.items[0].serial_and_batch_bundle)
|
||||
|
||||
batches = get_auto_batch_nos(
|
||||
frappe._dict(
|
||||
@@ -797,7 +806,24 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
)
|
||||
|
||||
for batch in batches:
|
||||
if batch.batch_no == "TestBatch-RS 02" and batch.warehouse == "_Test Warehouse - _TC":
|
||||
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
|
||||
self.assertEqual(batch.qty, 15)
|
||||
|
||||
# POS Invoice 2, for the batch with bundle
|
||||
pos_inv2 = create_pos_invoice(
|
||||
item="_BATCH ITEM Test For Reserve", rate=300, qty=10, batch_no=batch_no
|
||||
)
|
||||
pos_inv2.reload()
|
||||
self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle)
|
||||
|
||||
batches = get_auto_batch_nos(
|
||||
frappe._dict(
|
||||
{"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"}
|
||||
)
|
||||
)
|
||||
|
||||
for batch in batches:
|
||||
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
|
||||
self.assertEqual(batch.qty, 5)
|
||||
|
||||
def test_pos_batch_item_qty_validation(self):
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -833,7 +834,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-12 13:36:40.160468",
|
||||
"modified": "2023-11-14 18:33:22.585715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
|
||||
@@ -454,7 +454,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||
error_message = safe_load_json(message_log)
|
||||
error_message = get_error_message(message_log)
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status="Failed")
|
||||
@@ -483,7 +483,7 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||
error_message = safe_load_json(message_log)
|
||||
error_message = get_error_message(message_log)
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status="Submitted")
|
||||
@@ -525,10 +525,8 @@ def check_scheduler_status():
|
||||
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
||||
|
||||
|
||||
def safe_load_json(message):
|
||||
def get_error_message(message) -> str:
|
||||
try:
|
||||
json_message = json.loads(message).get("message")
|
||||
return message["message"]
|
||||
except Exception:
|
||||
json_message = message
|
||||
|
||||
return json_message
|
||||
return str(message)
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-21 17:36:26.642617",
|
||||
"modified": "2023-11-02 11:32:12.254018",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Payment Reconciliation Log",
|
||||
@@ -125,7 +125,19 @@
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
|
||||
@@ -384,7 +384,8 @@
|
||||
"label": "Supplier Invoice No",
|
||||
"oldfieldname": "bill_no",
|
||||
"oldfieldtype": "Data",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
@@ -407,7 +408,8 @@
|
||||
"no_copy": 1,
|
||||
"options": "Purchase Invoice",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_addresses",
|
||||
@@ -1602,7 +1604,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-16 16:24:51.886231",
|
||||
"modified": "2023-11-03 15:47:30.319200",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
@@ -1665,4 +1667,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ from erpnext.accounts.general_ledger import (
|
||||
)
|
||||
from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
@@ -281,9 +281,6 @@ class PurchaseInvoice(BuyingController):
|
||||
# in case of auto inventory accounting,
|
||||
# expense account is always "Stock Received But Not Billed" for a stock item
|
||||
# except opening entry, drop-ship entry and fixed asset items
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if (
|
||||
auto_accounting_for_stock
|
||||
and item.item_code in stock_items
|
||||
@@ -350,22 +347,26 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif item.is_fixed_asset:
|
||||
account_type = (
|
||||
"capital_work_in_progress_account"
|
||||
if is_cwip_accounting_enabled(item.asset_category)
|
||||
else "fixed_asset_account"
|
||||
)
|
||||
asset_category_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=item.item_code, company=self.company
|
||||
account_type, item=item.item_code, company=self.company
|
||||
)
|
||||
if not asset_category_account:
|
||||
form_link = get_link_to_form("Asset Category", asset_category)
|
||||
form_link = get_link_to_form("Asset Category", item.asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||
title=_("Missing Account"),
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||
|
||||
@@ -584,12 +585,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None):
|
||||
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||
|
||||
if self.auto_accounting_for_stock:
|
||||
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
||||
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
else:
|
||||
self.stock_received_but_not_billed = None
|
||||
self.expenses_included_in_valuation = None
|
||||
|
||||
self.negative_expense_to_be_booked = 0.0
|
||||
gl_entries = []
|
||||
@@ -598,9 +598,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_precision_loss_gl_entry(gl_entries)
|
||||
|
||||
if self.check_asset_cwip_enabled():
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
@@ -702,7 +699,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
|
||||
if (
|
||||
self.update_stock
|
||||
and self.auto_accounting_for_stock
|
||||
and (item.item_code in stock_items or item.is_fixed_asset)
|
||||
):
|
||||
# warehouse account
|
||||
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
||||
gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@@ -817,9 +818,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
elif not item.is_fixed_asset or (
|
||||
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
|
||||
):
|
||||
else:
|
||||
expense_account = (
|
||||
item.expense_account
|
||||
if (not item.enable_deferred_expense or self.is_return)
|
||||
@@ -912,40 +911,6 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
# If asset is bought through this document and not linked to PR
|
||||
if self.update_stock and item.landed_cost_voucher_amount:
|
||||
expenses_included_in_asset_valuation = self.get_company_default(
|
||||
"expenses_included_in_asset_valuation"
|
||||
)
|
||||
# Amount added through landed-cost-voucher
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expenses_included_in_asset_valuation,
|
||||
"against": expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": expenses_included_in_asset_valuation,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of asset bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
@@ -970,11 +935,17 @@ class PurchaseInvoice(BuyingController):
|
||||
(item.purchase_receipt, valuation_tax_accounts),
|
||||
)
|
||||
|
||||
stock_rbnb = (
|
||||
self.get_company_default("asset_received_but_not_billed")
|
||||
if item.is_fixed_asset
|
||||
else self.stock_received_but_not_billed
|
||||
)
|
||||
|
||||
if not negative_expense_booked_in_pr:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.stock_received_but_not_billed,
|
||||
"account": stock_rbnb,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
@@ -989,156 +960,12 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount, item.precision("item_tax_amount")
|
||||
)
|
||||
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
arbnb_account = None
|
||||
eiiav_account = None
|
||||
asset_eiiav_currency = None
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
|
||||
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
||||
|
||||
item_exp_acc_type = frappe.get_cached_value("Account", item.expense_account, "account_type")
|
||||
if not item.expense_account or item_exp_acc_type not in [
|
||||
"Asset Received But Not Billed",
|
||||
"Fixed Asset",
|
||||
]:
|
||||
if not arbnb_account:
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = arbnb_account
|
||||
|
||||
if not self.update_stock:
|
||||
arbnb_currency = get_account_currency(item.expense_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if arbnb_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount:
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"credit": item.item_tax_amount,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
else:
|
||||
cwip_account = get_asset_account(
|
||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
||||
)
|
||||
|
||||
cwip_account_currency = get_account_currency(cwip_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": self.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"credit": item.item_tax_amount,
|
||||
"project": item.project or self.project,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# Assets are bought through this document then it will be linked to this document
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
if not eiiav_account:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
return gl_entries
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@@ -1837,6 +1664,7 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"po_detail": "purchase_order_item",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
|
||||
|
||||
@@ -1783,9 +1783,14 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||
|
||||
def test_gl_entries_for_standalone_debit_note(self):
|
||||
make_purchase_invoice(qty=5, rate=500, update_stock=True)
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
|
||||
item_code = make_item(properties={"is_stock_item": 1})
|
||||
make_purchase_invoice(item_code=item_code, qty=5, rate=500, update_stock=True)
|
||||
|
||||
returned_inv = make_purchase_invoice(
|
||||
item_code=item_code, qty=-5, rate=5, update_stock=True, is_return=True
|
||||
)
|
||||
|
||||
# override the rate with valuation rate
|
||||
sle = frappe.get_all(
|
||||
@@ -1795,7 +1800,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
)[0]
|
||||
|
||||
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
|
||||
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
|
||||
self.assertAlmostEqual(rate, 500)
|
||||
|
||||
def test_payment_allocation_for_payment_terms(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import (
|
||||
@@ -1898,6 +1903,12 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
disable_dimension()
|
||||
|
||||
def test_repost_accounting_entries(self):
|
||||
# update repost settings
|
||||
settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||
if not [x for x in settings.allowed_types if x.document_type == "Purchase Invoice"]:
|
||||
settings.append("allowed_types", {"document_type": "Purchase Invoice", "allowed": True})
|
||||
settings.save()
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
rate=1000,
|
||||
price_list_rate=1000,
|
||||
@@ -1949,6 +1960,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
self.assertEqual(po.docstatus, 1)
|
||||
self.assertEqual(pi.docstatus, 1)
|
||||
|
||||
def test_default_cost_center_for_purchase(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
|
||||
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
|
||||
create_cost_center(cost_center_name=c_center)
|
||||
|
||||
item = create_item(
|
||||
"_Test Cost Center Item For Purchase",
|
||||
is_stock_item=1,
|
||||
buying_cost_center="_Test Cost Center Buying - _TC",
|
||||
selling_cost_center="_Test Cost Center Selling - _TC",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
|
||||
)
|
||||
|
||||
pi.items[0].cost_center = ""
|
||||
pi.set_missing_values()
|
||||
pi.calculate_taxes_and_totals()
|
||||
pi.save()
|
||||
|
||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -915,7 +916,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-03 21:01:01.824892",
|
||||
"modified": "2023-11-14 18:33:48.547297",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -5,9 +5,7 @@ frappe.ui.form.on("Repost Accounting Ledger", {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']],
|
||||
}
|
||||
query: "erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.get_repost_allowed_types"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,12 @@ from frappe.utils.data import comma_and
|
||||
class RepostAccountingLedger(Document):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
|
||||
self._allowed_types = set(
|
||||
["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"]
|
||||
)
|
||||
self._allowed_types = [
|
||||
x.document_type
|
||||
for x in frappe.db.get_all(
|
||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||
)
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
self.validate_vouchers()
|
||||
@@ -157,7 +160,7 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
doc.docstatus = 1
|
||||
doc.make_gl_entries()
|
||||
|
||||
elif doc.doctype in ["Payment Entry", "Journal Entry"]:
|
||||
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
|
||||
if not repost_doc.delete_cancelled_entries:
|
||||
doc.make_gl_entries(1)
|
||||
doc.make_gl_entries()
|
||||
@@ -186,3 +189,18 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
|
||||
filters = {"allowed": True}
|
||||
|
||||
if txt:
|
||||
filters.update({"document_type": ("like", f"%{txt}%")})
|
||||
|
||||
if allowed_types := frappe.db.get_all(
|
||||
"Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1
|
||||
):
|
||||
return allowed_types
|
||||
return []
|
||||
|
||||
@@ -20,10 +20,18 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.create_item()
|
||||
self.update_repost_settings()
|
||||
|
||||
def teadDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def update_repost_settings(self):
|
||||
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||
for x in allowed_types:
|
||||
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
||||
repost_settings.save()
|
||||
|
||||
def test_01_basic_functions(self):
|
||||
si = create_sales_invoice(
|
||||
item=self.item,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Repost Accounting Ledger Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2023-11-07 09:57:20.619939",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"allowed_types"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "allowed_types",
|
||||
"fieldtype": "Table",
|
||||
"label": "Allowed Doctypes",
|
||||
"options": "Repost Allowed Types"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-07 14:24:13.321522",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Accounting Ledger Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class RepostAccountingLedgerSettings(Document):
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestRepostAccountingLedgerSettings(FrappeTestCase):
|
||||
pass
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-11-07 09:58:03.595382",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"column_break_sfzb",
|
||||
"allowed"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Doctype",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allowed",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Allowed"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_sfzb",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-07 10:01:39.217861",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Allowed Types",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class RepostAllowedTypes(Document):
|
||||
pass
|
||||
@@ -187,7 +187,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
|
||||
make_maintenance_schedule() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
|
||||
@@ -563,15 +562,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
// Income Account in Details Table
|
||||
// --------------------------------
|
||||
cur_frm.set_query("income_account", "items", function(doc) {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {'company': doc.company}
|
||||
}
|
||||
});
|
||||
|
||||
// Cost Center in Details Table
|
||||
// -----------------------------
|
||||
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
|
||||
@@ -666,6 +656,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("income_account", "items", function() {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"is_return",
|
||||
"return_against",
|
||||
"update_billed_amount_in_sales_order",
|
||||
"update_billed_amount_in_delivery_note",
|
||||
"is_debit_note",
|
||||
"amended_from",
|
||||
"accounting_dimensions_section",
|
||||
@@ -2153,6 +2154,13 @@
|
||||
"fieldname": "use_company_roundoff_cost_center",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Company default Cost Center for Round off"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.is_return",
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -2165,7 +2173,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-07-25 16:02:18.988799",
|
||||
"modified": "2023-11-03 14:39:38.012346",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -253,6 +253,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.update_billing_status_in_dn()
|
||||
self.clear_unallocated_mode_of_payments()
|
||||
|
||||
@@ -1019,7 +1020,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def make_customer_gl_entry(self, gl_entries):
|
||||
# Checked both rounding_adjustment and rounded_total
|
||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||
# because rounded_total had value even before introduction of posting GLE based on rounded total
|
||||
grand_total = (
|
||||
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
)
|
||||
@@ -1267,7 +1268,7 @@ class SalesInvoice(SellingController):
|
||||
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||
payment_mode.base_amount -= flt(self.change_amount)
|
||||
|
||||
if payment_mode.amount:
|
||||
if payment_mode.base_amount:
|
||||
# POS, make payment entries
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@@ -1429,6 +1430,8 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
|
||||
def update_billing_status_in_dn(self, update_modified=True):
|
||||
if self.is_return and not self.update_billed_amount_in_delivery_note:
|
||||
return
|
||||
updated_delivery_notes = []
|
||||
for d in self.get("items"):
|
||||
if d.dn_detail:
|
||||
|
||||
@@ -2553,6 +2553,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
)
|
||||
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
si.customer = "_Test Internal Customer 3"
|
||||
si.update_stock = 1
|
||||
si.set_warehouse = "Finished Goods - _TC"
|
||||
si.set_target_warehouse = "Stores - _TC"
|
||||
@@ -3696,6 +3697,20 @@ def create_internal_parties():
|
||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer 3",
|
||||
represents_company="_Test Company",
|
||||
allowed_to_interact_with="_Test Company",
|
||||
)
|
||||
|
||||
account = create_account(
|
||||
account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "unrealized_profit_loss_account", account)
|
||||
|
||||
create_internal_supplier(
|
||||
supplier_name="_Test Internal Supplier",
|
||||
represents_company="Wind Power LLC",
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -901,7 +902,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-26 12:53:22.404057",
|
||||
"modified": "2023-11-14 18:34:10.479329",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
@@ -911,4 +912,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
from typing import Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe import _, msgprint, qb, scrub
|
||||
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
|
||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
||||
from frappe.model.utils import get_fetch_values
|
||||
@@ -480,11 +480,19 @@ def get_party_account_currency(party_type, party, company):
|
||||
|
||||
def get_party_gle_currency(party_type, party, company):
|
||||
def generator():
|
||||
existing_gle_currency = frappe.db.sql(
|
||||
"""select account_currency from `tabGL Entry`
|
||||
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
|
||||
limit 1""",
|
||||
{"company": company, "party_type": party_type, "party": party},
|
||||
gl = qb.DocType("GL Entry")
|
||||
existing_gle_currency = (
|
||||
qb.from_(gl)
|
||||
.select(gl.account_currency)
|
||||
.where(
|
||||
(gl.docstatus == 1)
|
||||
& (gl.company == company)
|
||||
& (gl.party_type == party_type)
|
||||
& (gl.party == party)
|
||||
& (gl.is_cancelled == 0)
|
||||
)
|
||||
.limit(1)
|
||||
.run()
|
||||
)
|
||||
|
||||
return existing_gle_currency[0][0] if existing_gle_currency else None
|
||||
|
||||
@@ -143,7 +143,18 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"fieldname": "show_future_payments",
|
||||
"label": __("Show Future Payments"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
@@ -175,4 +186,4 @@ function get_party_type_options() {
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -114,10 +114,13 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_group",
|
||||
"fieldname":"customer_group",
|
||||
"label": __("Customer Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group"
|
||||
"fieldtype": "MultiSelectList",
|
||||
"options": "Customer Group",
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Customer Group', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_terms_template",
|
||||
@@ -172,7 +175,19 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
|
||||
|
||||
],
|
||||
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
@@ -205,4 +220,4 @@ function get_party_type_options() {
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.utils import get_currency_precision
|
||||
from erpnext.accounts.utils import get_currency_precision, get_party_types_from_account_type
|
||||
|
||||
# This report gives a summary of all Outstanding Invoices considering the following
|
||||
|
||||
@@ -72,9 +72,7 @@ class ReceivablePayableReport(object):
|
||||
self.currency_precision = get_currency_precision() or 2
|
||||
self.dr_or_cr = "debit" if self.filters.account_type == "Receivable" else "credit"
|
||||
self.account_type = self.filters.account_type
|
||||
self.party_type = frappe.db.get_all(
|
||||
"Party Type", {"account_type": self.account_type}, pluck="name"
|
||||
)
|
||||
self.party_type = get_party_types_from_account_type(self.account_type)
|
||||
self.party_details = {}
|
||||
self.invoices = set()
|
||||
self.skip_total_row = 0
|
||||
@@ -116,7 +114,12 @@ class ReceivablePayableReport(object):
|
||||
# build all keys, since we want to exclude vouchers beyond the report date
|
||||
for ple in self.ple_entries:
|
||||
# get the balance object for voucher_type
|
||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||
|
||||
if not key in self.voucher_balance:
|
||||
self.voucher_balance[key] = frappe._dict(
|
||||
voucher_type=ple.voucher_type,
|
||||
@@ -183,7 +186,10 @@ class ReceivablePayableReport(object):
|
||||
):
|
||||
return
|
||||
|
||||
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
|
||||
# If payment is made against credit note
|
||||
# and credit note is made against a Sales Invoice
|
||||
@@ -192,13 +198,19 @@ class ReceivablePayableReport(object):
|
||||
if ple.against_voucher_no in self.return_entries:
|
||||
return_against = self.return_entries.get(ple.against_voucher_no)
|
||||
if return_against:
|
||||
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.against_voucher_type, return_against, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
|
||||
|
||||
row = self.voucher_balance.get(key)
|
||||
|
||||
if not row:
|
||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
||||
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
|
||||
if self.filters.get("ignore_accounts"):
|
||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||
else:
|
||||
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
|
||||
|
||||
row.party_type = ple.party_type
|
||||
return row
|
||||
@@ -267,11 +279,20 @@ 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)
|
||||
):
|
||||
must_consider = False
|
||||
if self.filters.get("for_revaluation_journals"):
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) or (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
):
|
||||
must_consider = True
|
||||
else:
|
||||
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)
|
||||
):
|
||||
must_consider = True
|
||||
|
||||
if must_consider:
|
||||
# non-zero oustanding, we must consider this row
|
||||
|
||||
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
||||
@@ -718,6 +739,7 @@ class ReceivablePayableReport(object):
|
||||
query = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.name,
|
||||
ple.account,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
@@ -731,13 +753,15 @@ class ReceivablePayableReport(object):
|
||||
ple.account_currency,
|
||||
ple.amount,
|
||||
ple.amount_in_account_currency,
|
||||
ple.remarks,
|
||||
)
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(self.qb_selection_filter))
|
||||
.where(Criterion.any(self.or_filters))
|
||||
)
|
||||
|
||||
if self.filters.get("show_remarks"):
|
||||
query = query.select(ple.remarks)
|
||||
|
||||
if self.filters.get("group_by_party"):
|
||||
query = query.orderby(self.ple.party, self.ple.posting_date)
|
||||
else:
|
||||
@@ -823,7 +847,13 @@ class ReceivablePayableReport(object):
|
||||
self.customer = qb.DocType("Customer")
|
||||
|
||||
if self.filters.get("customer_group"):
|
||||
self.get_hierarchical_filters("Customer Group", "customer_group")
|
||||
groups = get_customer_group_with_children(self.filters.customer_group)
|
||||
customers = (
|
||||
qb.from_(self.customer)
|
||||
.select(self.customer.name)
|
||||
.where(self.customer["customer_group"].isin(groups))
|
||||
)
|
||||
self.qb_selection_filter.append(self.ple.party.isin(customers))
|
||||
|
||||
if self.filters.get("territory"):
|
||||
self.get_hierarchical_filters("Territory", "territory")
|
||||
@@ -1115,3 +1145,19 @@ class ReceivablePayableReport(object):
|
||||
.run()
|
||||
)
|
||||
self.err_journals = [x[0] for x in results] if results else []
|
||||
|
||||
|
||||
def get_customer_group_with_children(customer_groups):
|
||||
if not isinstance(customer_groups, list):
|
||||
customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d]
|
||||
|
||||
all_customer_groups = []
|
||||
for d in customer_groups:
|
||||
if frappe.db.exists("Customer Group", d):
|
||||
lft, rgt = frappe.db.get_value("Customer Group", d, ["lft", "rgt"])
|
||||
children = frappe.get_all("Customer Group", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
|
||||
all_customer_groups += [c.name for c in children]
|
||||
else:
|
||||
frappe.throw(_("Customer Group: {0} does not exist").format(d))
|
||||
|
||||
return list(set(all_customer_groups))
|
||||
|
||||
@@ -475,6 +475,30 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
report = execute(filters)[1]
|
||||
self.assertEqual(len(report), 0)
|
||||
|
||||
def test_multi_customer_group_filter(self):
|
||||
si = self.create_sales_invoice()
|
||||
cus_group = frappe.db.get_value("Customer", self.customer, "customer_group")
|
||||
# Create a list of customer groups, e.g., ["Group1", "Group2"]
|
||||
cus_groups_list = [cus_group, "_Test Customer Group 1"]
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"customer_group": cus_groups_list, # Use the list of customer groups
|
||||
}
|
||||
report = execute(filters)[1]
|
||||
|
||||
# Assert that the report contains data for the specified customer groups
|
||||
self.assertTrue(len(report) > 0)
|
||||
|
||||
for row in report:
|
||||
# Assert that the customer group of each row is in the list of customer groups
|
||||
self.assertIn(row.customer_group, cus_groups_list)
|
||||
|
||||
def test_party_account_filter(self):
|
||||
si1 = self.create_sales_invoice()
|
||||
self.customer2 = (
|
||||
|
||||
@@ -139,6 +139,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
"label": __("Show GL Balance"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
onload: function(report) {
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
||||
from erpnext.accounts.utils import get_party_types_from_account_type
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -22,9 +23,7 @@ def execute(filters=None):
|
||||
class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
def run(self, args):
|
||||
self.account_type = args.get("account_type")
|
||||
self.party_type = frappe.db.get_all(
|
||||
"Party Type", {"account_type": self.account_type}, pluck="name"
|
||||
)
|
||||
self.party_type = get_party_types_from_account_type(self.account_type)
|
||||
self.party_naming_by = frappe.db.get_value(
|
||||
args.get("naming_by")[0], None, args.get("naming_by")[1]
|
||||
)
|
||||
|
||||
@@ -31,6 +31,18 @@ frappe.query_reports["Asset Depreciation Ledger"] = {
|
||||
"fieldtype": "Link",
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"fieldname":"asset_category",
|
||||
"label": __("Asset Category"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Asset Category"
|
||||
},
|
||||
{
|
||||
"fieldname":"cost_center",
|
||||
"label": __("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
@@ -38,10 +50,10 @@ frappe.query_reports["Asset Depreciation Ledger"] = {
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"asset_category",
|
||||
"label": __("Asset Category"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Asset Category"
|
||||
}
|
||||
"fieldname": "include_default_book_assets",
|
||||
"label": __("Include Default FB Assets"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2016-04-08 14:49:58.133098",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 2,
|
||||
"idx": 6,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2023-07-26 21:05:33.554778",
|
||||
"modified": "2023-11-08 20:17:05.774211",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Asset Depreciation Ledger",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import cstr, flt
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -32,7 +32,6 @@ def get_data(filters):
|
||||
filters_data.append(["against_voucher", "=", filters.get("asset")])
|
||||
|
||||
if filters.get("asset_category"):
|
||||
|
||||
assets = frappe.db.sql_list(
|
||||
"""select name from tabAsset
|
||||
where asset_category = %s and docstatus=1""",
|
||||
@@ -41,12 +40,27 @@ def get_data(filters):
|
||||
|
||||
filters_data.append(["against_voucher", "in", assets])
|
||||
|
||||
if filters.get("finance_book"):
|
||||
filters_data.append(["finance_book", "in", ["", filters.get("finance_book")]])
|
||||
company_fb = frappe.get_cached_value("Company", filters.get("company"), "default_finance_book")
|
||||
|
||||
if filters.get("include_default_book_assets") and company_fb:
|
||||
if filters.get("finance_book") and cstr(filters.get("finance_book")) != cstr(company_fb):
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Assets'"))
|
||||
else:
|
||||
finance_book = company_fb
|
||||
elif filters.get("finance_book"):
|
||||
finance_book = filters.get("finance_book")
|
||||
else:
|
||||
finance_book = None
|
||||
|
||||
if finance_book:
|
||||
or_filters_data = [["finance_book", "in", ["", finance_book]], ["finance_book", "is", "not set"]]
|
||||
else:
|
||||
or_filters_data = [["finance_book", "in", [""]], ["finance_book", "is", "not set"]]
|
||||
|
||||
gl_entries = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters=filters_data,
|
||||
or_filters=or_filters_data,
|
||||
fields=["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"],
|
||||
order_by="against_voucher, posting_date",
|
||||
)
|
||||
@@ -61,7 +75,9 @@ def get_data(filters):
|
||||
asset_data = assets_details.get(d.against_voucher)
|
||||
if asset_data:
|
||||
if not asset_data.get("accumulated_depreciation_amount"):
|
||||
asset_data.accumulated_depreciation_amount = d.debit
|
||||
asset_data.accumulated_depreciation_amount = d.debit + asset_data.get(
|
||||
"opening_accumulated_depreciation"
|
||||
)
|
||||
else:
|
||||
asset_data.accumulated_depreciation_amount += d.debit
|
||||
|
||||
@@ -70,7 +86,7 @@ def get_data(filters):
|
||||
{
|
||||
"depreciation_amount": d.debit,
|
||||
"depreciation_date": d.posting_date,
|
||||
"amount_after_depreciation": (
|
||||
"value_after_depreciation": (
|
||||
flt(row.gross_purchase_amount) - flt(row.accumulated_depreciation_amount)
|
||||
),
|
||||
"depreciation_entry": d.voucher_no,
|
||||
@@ -88,10 +104,12 @@ def get_assets_details(assets):
|
||||
fields = [
|
||||
"name as asset",
|
||||
"gross_purchase_amount",
|
||||
"opening_accumulated_depreciation",
|
||||
"asset_category",
|
||||
"status",
|
||||
"depreciation_method",
|
||||
"purchase_date",
|
||||
"cost_center",
|
||||
]
|
||||
|
||||
for d in frappe.get_all("Asset", fields=fields, filters={"name": ("in", assets)}):
|
||||
@@ -121,6 +139,12 @@ def get_columns():
|
||||
"fieldtype": "Currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Opening Accumulated Depreciation"),
|
||||
"fieldname": "opening_accumulated_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"width": 140,
|
||||
},
|
||||
{
|
||||
"label": _("Depreciation Amount"),
|
||||
"fieldname": "depreciation_amount",
|
||||
@@ -134,8 +158,8 @@ def get_columns():
|
||||
"width": 210,
|
||||
},
|
||||
{
|
||||
"label": _("Amount After Depreciation"),
|
||||
"fieldname": "amount_after_depreciation",
|
||||
"label": _("Value After Depreciation"),
|
||||
"fieldname": "value_after_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"width": 180,
|
||||
},
|
||||
@@ -153,12 +177,13 @@ def get_columns():
|
||||
"options": "Asset Category",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
|
||||
{
|
||||
"label": _("Depreciation Method"),
|
||||
"fieldname": "depreciation_method",
|
||||
"fieldtype": "Data",
|
||||
"width": 130,
|
||||
"label": _("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "cost_center",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
|
||||
{"label": _("Purchase Date"), "fieldname": "purchase_date", "fieldtype": "Date", "width": 120},
|
||||
]
|
||||
|
||||
@@ -17,7 +17,7 @@ frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||
|
||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||
fieldname: "include_default_book_entries",
|
||||
label: __("Include Default Book Entries"),
|
||||
label: __("Include Default FB Entries"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ frappe.query_reports["Cash Flow"]["filters"].splice(8, 1);
|
||||
frappe.query_reports["Cash Flow"]["filters"].push(
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"label": __("Include Default FB Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ frappe.query_reports["Consolidated Financial Statement"] = {
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"label": __("Include Default FB Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
|
||||
@@ -561,9 +561,7 @@ def apply_additional_conditions(doctype, query, from_date, ignore_closing_entrie
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
|
||||
|
||||
query = query.where(
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
|
||||
@@ -79,7 +79,9 @@ class General_Payment_Ledger_Comparison(object):
|
||||
.select(
|
||||
gle.company,
|
||||
gle.account,
|
||||
gle.voucher_type,
|
||||
gle.voucher_no,
|
||||
gle.party_type,
|
||||
gle.party,
|
||||
outstanding,
|
||||
)
|
||||
@@ -89,7 +91,9 @@ class General_Payment_Ledger_Comparison(object):
|
||||
& (gle.account.isin(val.accounts))
|
||||
)
|
||||
.where(Criterion.all(filter_criterion))
|
||||
.groupby(gle.company, gle.account, gle.voucher_no, gle.party)
|
||||
.groupby(
|
||||
gle.company, gle.account, gle.voucher_type, gle.voucher_no, gle.party_type, gle.party
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
@@ -112,7 +116,13 @@ class General_Payment_Ledger_Comparison(object):
|
||||
self.account_types[acc_type].ple = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding")
|
||||
ple.company,
|
||||
ple.account,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
Sum(ple.amount).as_("outstanding"),
|
||||
)
|
||||
.where(
|
||||
(ple.company == self.filters.company)
|
||||
@@ -120,7 +130,9 @@ class General_Payment_Ledger_Comparison(object):
|
||||
& (ple.account.isin(val.accounts))
|
||||
)
|
||||
.where(Criterion.all(filter_criterion))
|
||||
.groupby(ple.company, ple.account, ple.voucher_no, ple.party)
|
||||
.groupby(
|
||||
ple.company, ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
@@ -138,12 +150,12 @@ class General_Payment_Ledger_Comparison(object):
|
||||
self.diff = frappe._dict({})
|
||||
|
||||
for x in self.variation_in_payment_ledger:
|
||||
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
|
||||
self.diff[(x[0], x[1], x[2], x[3], x[4], x[5])] = frappe._dict({"gl_balance": x[6]})
|
||||
|
||||
for x in self.variation_in_general_ledger:
|
||||
self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update(
|
||||
frappe._dict({"pl_balance": x[4]})
|
||||
)
|
||||
self.diff.setdefault(
|
||||
(x[0], x[1], x[2], x[3], x[4], x[5]), frappe._dict({"gl_balance": 0.0})
|
||||
).update(frappe._dict({"pl_balance": x[6]}))
|
||||
|
||||
def generate_data(self):
|
||||
self.data = []
|
||||
@@ -151,8 +163,12 @@ class General_Payment_Ledger_Comparison(object):
|
||||
self.data.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"voucher_no": key[2],
|
||||
"party": key[3],
|
||||
"company": key[0],
|
||||
"account": key[1],
|
||||
"voucher_type": key[2],
|
||||
"voucher_no": key[3],
|
||||
"party_type": key[4],
|
||||
"party": key[5],
|
||||
"gl_balance": val.gl_balance,
|
||||
"pl_balance": val.pl_balance,
|
||||
}
|
||||
@@ -162,12 +178,52 @@ class General_Payment_Ledger_Comparison(object):
|
||||
def get_columns(self):
|
||||
self.columns = []
|
||||
options = None
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Company"),
|
||||
fieldname="company",
|
||||
fieldtype="Link",
|
||||
options="Company",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Account"),
|
||||
fieldname="account",
|
||||
fieldtype="Link",
|
||||
options="Account",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Voucher Type"),
|
||||
fieldname="voucher_type",
|
||||
fieldtype="Link",
|
||||
options="DocType",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Voucher No"),
|
||||
fieldname="voucher_no",
|
||||
fieldtype="Data",
|
||||
options=options,
|
||||
fieldtype="Dynamic Link",
|
||||
options="voucher_type",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Party Type"),
|
||||
fieldname="party_type",
|
||||
fieldtype="Link",
|
||||
options="DocType",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
@@ -176,8 +232,8 @@ class General_Payment_Ledger_Comparison(object):
|
||||
dict(
|
||||
label=_("Party"),
|
||||
fieldname="party",
|
||||
fieldtype="Data",
|
||||
options=options,
|
||||
fieldtype="Dynamic Link",
|
||||
options="party_type",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -50,7 +50,11 @@ class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin):
|
||||
self.assertEqual(len(data), 1)
|
||||
|
||||
expected = {
|
||||
"company": sinv.company,
|
||||
"account": sinv.debit_to,
|
||||
"voucher_type": sinv.doctype,
|
||||
"voucher_no": sinv.name,
|
||||
"party_type": "Customer",
|
||||
"party": sinv.customer,
|
||||
"gl_balance": sinv.grand_total,
|
||||
"pl_balance": sinv.grand_total - 1,
|
||||
|
||||
@@ -175,7 +175,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"label": __("Include Default FB Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
@@ -193,7 +193,13 @@ frappe.query_reports["General Ledger"] = {
|
||||
"fieldname": "add_values_in_transaction_currency",
|
||||
"label": __("Add Columns in Transaction Currency"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,9 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
select_fields = """, debit, credit, debit_in_account_currency,
|
||||
credit_in_account_currency """
|
||||
|
||||
if filters.get("show_remarks"):
|
||||
select_fields += """,remarks"""
|
||||
|
||||
order_by_statement = "order by posting_date, account, creation"
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
@@ -195,7 +198,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
voucher_type, voucher_no, {dimension_fields}
|
||||
cost_center, project, {transaction_currency_fields}
|
||||
against_voucher_type, against_voucher, account_currency,
|
||||
remarks, against, is_opening, creation {select_fields}
|
||||
against, is_opening, creation {select_fields}
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s {conditions}
|
||||
{order_by_statement}
|
||||
@@ -256,9 +259,7 @@ def get_conditions(filters):
|
||||
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||
filters.get("company_fb")
|
||||
):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
|
||||
else:
|
||||
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
@@ -631,8 +632,10 @@ def get_columns(filters):
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100},
|
||||
{"label": _("Remarks"), "fieldname": "remarks", "width": 400},
|
||||
]
|
||||
)
|
||||
|
||||
if filters.get("show_remarks"):
|
||||
columns.extend([{"label": _("Remarks"), "fieldname": "remarks", "width": 400}])
|
||||
|
||||
return columns
|
||||
|
||||
@@ -32,13 +32,6 @@ frappe.query_reports["Profitability Analysis"] = {
|
||||
"label": __("Accounting Dimension"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Accounting Dimension",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
|
||||
@@ -47,6 +47,7 @@ def get_result(
|
||||
out = []
|
||||
for name, details in gle_map.items():
|
||||
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
|
||||
bill_no, bill_date = "", ""
|
||||
tax_withholding_category = tax_category_map.get(name)
|
||||
rate = tax_rate_map.get(tax_withholding_category)
|
||||
|
||||
@@ -70,7 +71,10 @@ def get_result(
|
||||
if net_total_map.get(name):
|
||||
if voucher_type == "Journal Entry":
|
||||
# back calcalute total amount from rate and tax_amount
|
||||
total_amount = grand_total = base_total = tax_amount / (rate / 100)
|
||||
if rate:
|
||||
total_amount = grand_total = base_total = tax_amount / (rate / 100)
|
||||
elif voucher_type == "Purchase Invoice":
|
||||
total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(name)
|
||||
else:
|
||||
total_amount, grand_total, base_total = net_total_map.get(name)
|
||||
else:
|
||||
@@ -96,7 +100,7 @@ def get_result(
|
||||
|
||||
row.update(
|
||||
{
|
||||
"section_code": tax_withholding_category,
|
||||
"section_code": tax_withholding_category or "",
|
||||
"entity_type": party_map.get(party, {}).get(party_type),
|
||||
"rate": rate,
|
||||
"total_amount": total_amount,
|
||||
@@ -106,10 +110,14 @@ def get_result(
|
||||
"transaction_date": posting_date,
|
||||
"transaction_type": voucher_type,
|
||||
"ref_no": name,
|
||||
"supplier_invoice_no": bill_no,
|
||||
"supplier_invoice_date": bill_date,
|
||||
}
|
||||
)
|
||||
out.append(row)
|
||||
|
||||
out.sort(key=lambda x: x["section_code"])
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@@ -157,14 +165,14 @@ def get_gle_map(documents):
|
||||
def get_columns(filters):
|
||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||
columns = [
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
"label": _("Section Code"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldtype": "Link",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||
]
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
@@ -179,51 +187,60 @@ def get_columns(filters):
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldtype": "Link",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||
{
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
},
|
||||
]
|
||||
)
|
||||
if filters.party_type == "Supplier":
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Supplier Invoice No"),
|
||||
"fieldname": "supplier_invoice_no",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Supplier Invoice Date"),
|
||||
"fieldname": "supplier_invoice_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 90,
|
||||
"width": 60,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Base Total"),
|
||||
"fieldname": "base_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
|
||||
{
|
||||
"label": _("Reference No."),
|
||||
"fieldname": "ref_no",
|
||||
@@ -231,6 +248,12 @@ def get_columns(filters):
|
||||
"options": "transaction_type",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
@@ -253,27 +276,7 @@ def get_tds_docs(filters):
|
||||
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
|
||||
)
|
||||
|
||||
query_filters = {
|
||||
"account": ("in", tds_accounts),
|
||||
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
|
||||
"is_cancelled": 0,
|
||||
"against": ("not in", bank_accounts),
|
||||
}
|
||||
|
||||
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||
or_filters.update({"against": ("in", party), "voucher_type": "Journal Entry"})
|
||||
|
||||
if filters.get("party"):
|
||||
del query_filters["account"]
|
||||
del query_filters["against"]
|
||||
or_filters = {"against": filters.get("party"), "party": filters.get("party")}
|
||||
|
||||
tds_docs = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters=query_filters,
|
||||
or_filters=or_filters,
|
||||
fields=["voucher_no", "voucher_type", "against", "party"],
|
||||
)
|
||||
tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
|
||||
|
||||
for d in tds_docs:
|
||||
if d.voucher_type == "Purchase Invoice":
|
||||
@@ -309,6 +312,47 @@ def get_tds_docs(filters):
|
||||
)
|
||||
|
||||
|
||||
def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
||||
if not tds_accounts:
|
||||
frappe.throw(
|
||||
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
|
||||
title=_("Accounts Missing Error"),
|
||||
)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
query = (
|
||||
frappe.qb.from_(gle)
|
||||
.select("voucher_no", "voucher_type", "against", "party")
|
||||
.where((gle.is_cancelled == 0))
|
||||
)
|
||||
|
||||
if filters.get("from_date"):
|
||||
query = query.where(gle.posting_date >= filters.get("from_date"))
|
||||
if filters.get("to_date"):
|
||||
query = query.where(gle.posting_date <= filters.get("to_date"))
|
||||
|
||||
if bank_accounts:
|
||||
query = query.where(gle.against.notin(bank_accounts))
|
||||
|
||||
if filters.get("party"):
|
||||
party = [filters.get("party")]
|
||||
query = query.where(
|
||||
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||
| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
|
||||
| gle.party.isin(party)
|
||||
)
|
||||
else:
|
||||
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||
query = query.where(
|
||||
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||
| (
|
||||
(gle.voucher_type == "Journal Entry")
|
||||
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
|
||||
)
|
||||
| gle.party.isin(party)
|
||||
)
|
||||
return query
|
||||
|
||||
|
||||
def get_journal_entry_party_map(journal_entries):
|
||||
journal_entry_party_map = {}
|
||||
for d in frappe.db.get_all(
|
||||
@@ -335,6 +379,8 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
"base_tax_withholding_net_total",
|
||||
"grand_total",
|
||||
"base_total",
|
||||
"bill_no",
|
||||
"bill_date",
|
||||
],
|
||||
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
|
||||
"Payment Entry": [
|
||||
@@ -353,7 +399,13 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
for entry in entries:
|
||||
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
||||
if doctype == "Purchase Invoice":
|
||||
value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]
|
||||
value = [
|
||||
entry.base_tax_withholding_net_total,
|
||||
entry.grand_total,
|
||||
entry.base_total,
|
||||
entry.bill_no,
|
||||
entry.bill_date,
|
||||
]
|
||||
elif doctype == "Sales Invoice":
|
||||
value = [entry.base_net_total, entry.grand_total, entry.base_total]
|
||||
elif doctype == "Payment Entry":
|
||||
|
||||
@@ -95,7 +95,7 @@ frappe.query_reports["Trial Balance"] = {
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"label": __("Include Default FB Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
|
||||
@@ -275,9 +275,7 @@ def get_opening_balance(
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
|
||||
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
|
||||
@@ -53,6 +53,9 @@ GL_REPOSTING_CHUNK = 100
|
||||
def get_fiscal_year(
|
||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
|
||||
):
|
||||
if isinstance(boolean, str):
|
||||
boolean = frappe.json.loads(boolean)
|
||||
|
||||
fiscal_years = get_fiscal_years(
|
||||
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
|
||||
)
|
||||
@@ -2040,3 +2043,7 @@ def create_gain_loss_journal(
|
||||
journal_entry.save()
|
||||
journal_entry.submit()
|
||||
return journal_entry.name
|
||||
|
||||
|
||||
def get_party_types_from_account_type(account_type):
|
||||
return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name")
|
||||
|
||||
@@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', {
|
||||
frm.set_query("item_code", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"disabled": 0,
|
||||
"is_fixed_asset": 1,
|
||||
"is_stock_item": 0
|
||||
}
|
||||
|
||||
@@ -221,11 +221,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)",
|
||||
"fieldname": "gross_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Gross Purchase Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -399,6 +399,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Receipt",
|
||||
@@ -416,6 +417,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
@@ -562,9 +564,14 @@
|
||||
"link_doctype": "Journal Entry",
|
||||
"link_fieldname": "reference_name",
|
||||
"table_fieldname": "accounts"
|
||||
},
|
||||
{
|
||||
"group": "Asset Capitalization",
|
||||
"link_doctype": "Asset Capitalization",
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-03 23:28:26.732269",
|
||||
"modified": "2023-11-15 17:40:17.315203",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
@@ -608,4 +615,4 @@
|
||||
"states": [],
|
||||
"title_field": "asset_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -818,7 +818,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||
"depreciation_method": d.depreciation_method,
|
||||
"total_number_of_depreciations": d.total_number_of_depreciations,
|
||||
"frequency_of_depreciation": d.frequency_of_depreciation,
|
||||
"daily_depreciation": d.daily_depreciation,
|
||||
"daily_prorata_based": d.daily_prorata_based,
|
||||
"salvage_value_percentage": d.salvage_value_percentage,
|
||||
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
||||
* flt(d.salvage_value_percentage / 100),
|
||||
|
||||
@@ -780,6 +780,15 @@ def get_disposal_account_and_cost_center(company):
|
||||
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
|
||||
asset_doc = frappe.get_doc("Asset", asset)
|
||||
|
||||
if asset_doc.available_for_use_date > getdate(disposal_date):
|
||||
frappe.throw(
|
||||
"Disposal date {0} cannot be before available for use date {1} of the asset.".format(
|
||||
disposal_date, asset_doc.available_for_use_date
|
||||
)
|
||||
)
|
||||
elif asset_doc.available_for_use_date == getdate(disposal_date):
|
||||
return flt(asset_doc.gross_purchase_amount - asset_doc.opening_accumulated_depreciation)
|
||||
|
||||
if not asset_doc.calculate_depreciation:
|
||||
return flt(asset_doc.value_after_depreciation)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ from frappe.utils import (
|
||||
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 (
|
||||
get_asset_value_after_depreciation,
|
||||
make_sales_invoice,
|
||||
split_asset,
|
||||
update_maintenance_status,
|
||||
@@ -194,6 +193,7 @@ class TestAsset(AssetSetup):
|
||||
def test_is_fixed_asset_set(self):
|
||||
asset = create_asset(is_existing_asset=1)
|
||||
doc = frappe.new_doc("Purchase Invoice")
|
||||
doc.company = "_Test Company"
|
||||
doc.supplier = "_Test Supplier"
|
||||
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
|
||||
|
||||
@@ -534,7 +534,7 @@ class TestAsset(AssetSetup):
|
||||
|
||||
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
|
||||
|
||||
# CWIP: Capital Work In Progress
|
||||
# Capital Work In Progress
|
||||
def test_cwip_accounting(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
|
||||
@@ -567,7 +567,8 @@ class TestAsset(AssetSetup):
|
||||
pr.submit()
|
||||
|
||||
expected_gle = (
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||
("_Test Account Shipping Charges - _TC", 0.0, 250.0),
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
|
||||
("CWIP Account - _TC", 5250.0, 0.0),
|
||||
)
|
||||
|
||||
@@ -586,9 +587,8 @@ class TestAsset(AssetSetup):
|
||||
expected_gle = (
|
||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5000.0, 0.0),
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||
)
|
||||
|
||||
pi_gle = frappe.db.sql(
|
||||
@@ -755,7 +755,9 @@ class TestDepreciationMethods(AssetSetup):
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_schedule_for_straight_line_method_with_daily_depreciation(self):
|
||||
def test_schedule_for_straight_line_method_with_daily_prorata_based(
|
||||
self,
|
||||
):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2023-01-01",
|
||||
@@ -764,7 +766,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
depreciation_start_date="2023-01-31",
|
||||
total_number_of_depreciations=12,
|
||||
frequency_of_depreciation=1,
|
||||
daily_depreciation=1,
|
||||
daily_prorata_based=1,
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
@@ -1760,7 +1762,7 @@ def create_asset(**args):
|
||||
"total_number_of_depreciations": args.total_number_of_depreciations or 5,
|
||||
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
|
||||
"depreciation_start_date": args.depreciation_start_date,
|
||||
"daily_depreciation": args.daily_depreciation or 0,
|
||||
"daily_prorata_based": args.daily_prorata_based or 0,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1789,6 +1791,7 @@ def create_asset_category():
|
||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||
"depreciation_expense_account": "_Test Depreciations - _TC",
|
||||
"capital_work_in_progress_account": "CWIP Account - _TC",
|
||||
},
|
||||
)
|
||||
asset_category.append(
|
||||
|
||||
@@ -876,12 +876,8 @@ def get_items_tagged_to_wip_composite_asset(asset):
|
||||
"amount",
|
||||
]
|
||||
|
||||
pi_items = frappe.get_all(
|
||||
"Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||
)
|
||||
|
||||
pr_items = frappe.get_all(
|
||||
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||
)
|
||||
|
||||
return pi_items + pr_items
|
||||
return pr_items
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"depreciation_method",
|
||||
"total_number_of_depreciations",
|
||||
"rate_of_depreciation",
|
||||
"daily_depreciation",
|
||||
"daily_prorata_based",
|
||||
"column_break_8",
|
||||
"frequency_of_depreciation",
|
||||
"expected_value_after_useful_life",
|
||||
@@ -179,9 +179,9 @@
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
||||
"fieldname": "daily_depreciation",
|
||||
"fieldname": "daily_prorata_based",
|
||||
"fieldtype": "Check",
|
||||
"label": "Daily Depreciation",
|
||||
"label": "Depreciate based on daily pro-rata",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
@@ -189,7 +189,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-10 22:22:09.722968",
|
||||
"modified": "2023-11-03 21:32:15.021796",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Depreciation Schedule",
|
||||
|
||||
@@ -153,7 +153,7 @@ class AssetDepreciationSchedule(Document):
|
||||
self.frequency_of_depreciation = row.frequency_of_depreciation
|
||||
self.rate_of_depreciation = row.rate_of_depreciation
|
||||
self.expected_value_after_useful_life = row.expected_value_after_useful_life
|
||||
self.daily_depreciation = row.daily_depreciation
|
||||
self.daily_prorata_based = row.daily_prorata_based
|
||||
self.status = "Draft"
|
||||
|
||||
def make_depr_schedule(
|
||||
@@ -573,7 +573,7 @@ def get_straight_line_or_manual_depr_amount(
|
||||
)
|
||||
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
||||
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
if row.daily_depreciation:
|
||||
if row.daily_prorata_based:
|
||||
daily_depr_amount = (
|
||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||
) / date_diff(
|
||||
@@ -618,7 +618,7 @@ def get_straight_line_or_manual_depr_amount(
|
||||
) / number_of_pending_depreciations
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
else:
|
||||
if row.daily_depreciation:
|
||||
if row.daily_prorata_based:
|
||||
daily_depr_amount = (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"finance_book",
|
||||
"depreciation_method",
|
||||
"total_number_of_depreciations",
|
||||
"daily_depreciation",
|
||||
"daily_prorata_based",
|
||||
"column_break_5",
|
||||
"frequency_of_depreciation",
|
||||
"depreciation_start_date",
|
||||
@@ -86,23 +86,23 @@
|
||||
"fieldtype": "Percent",
|
||||
"label": "Rate of Depreciation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
||||
"fieldname": "daily_depreciation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Daily Depreciation"
|
||||
},
|
||||
{
|
||||
"fieldname": "salvage_value_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Salvage Value Percentage"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
||||
"fieldname": "daily_prorata_based",
|
||||
"fieldtype": "Check",
|
||||
"label": "Depreciate based on daily pro-rata"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-29 15:39:52.740594",
|
||||
"modified": "2023-11-03 21:30:24.266601",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
||||
@@ -52,7 +52,7 @@ frappe.query_reports["Fixed Asset Register"] = {
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_assets",
|
||||
"label": __("Include Default Book Assets"),
|
||||
"label": __("Include Default FB Assets"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
|
||||
@@ -223,7 +223,7 @@ def get_assets_linked_to_fb(filters):
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'"))
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Assets'"))
|
||||
|
||||
query = query.where(
|
||||
(afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Bulk Transaction Log', {
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
|
||||
frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
|
||||
query(frm, 1);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
frappe.ui.form.on("Bulk Transaction Log", {
|
||||
refresh(frm) {
|
||||
frm.add_custom_button(__('Succeeded Entries'), function() {
|
||||
frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Success"});
|
||||
}, __("View"));
|
||||
frm.add_custom_button(__('Failed Entries'), function() {
|
||||
frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"});
|
||||
}, __("View"));
|
||||
if (frm.doc.failed) {
|
||||
frm.add_custom_button(__('Retry Failed Transactions'), function() {
|
||||
frappe.call({
|
||||
method: "erpnext.utilities.bulk_transaction.retry",
|
||||
args: {date: frm.doc.date}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function query(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
||||
args: {
|
||||
log_date: frm.doc.log_date
|
||||
}
|
||||
}).then((r) => {
|
||||
if (r.message === "No Failed Records") {
|
||||
frappe.show_alert(__(r.message), 5);
|
||||
} else {
|
||||
frappe.show_alert(__("Retrying Failed Transactions"), 5);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,31 +1,64 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2021-11-30 13:41:16.343827",
|
||||
"allow_copy": 1,
|
||||
"creation": "2023-11-09 20:14:45.139593",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"log_date",
|
||||
"logger_data"
|
||||
"date",
|
||||
"column_break_bsan",
|
||||
"log_entries",
|
||||
"section_break_mdmv",
|
||||
"succeeded",
|
||||
"column_break_qryp",
|
||||
"failed"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "log_date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Log Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "logger_data",
|
||||
"fieldtype": "Table",
|
||||
"label": "Logger Data",
|
||||
"options": "Bulk Transaction Log Detail"
|
||||
"fieldname": "log_entries",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Log Entries",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bsan",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_mdmv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "succeeded",
|
||||
"fieldtype": "Int",
|
||||
"label": "Succeeded",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qryp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "failed",
|
||||
"fieldtype": "Int",
|
||||
"label": "Failed",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"in_create": 1,
|
||||
"is_virtual": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-03 17:23:02.935325",
|
||||
"modified": "2023-11-11 04:52:49.347376",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Bulk Transaction",
|
||||
"name": "Bulk Transaction Log",
|
||||
@@ -47,5 +80,5 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
"title_field": "date"
|
||||
}
|
||||
@@ -1,67 +1,112 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.utilities.bulk_transaction import task, update_logger
|
||||
from frappe.query_builder.functions import Count
|
||||
from frappe.utils import cint
|
||||
from pypika import Order
|
||||
|
||||
|
||||
class BulkTransactionLog(Document):
|
||||
pass
|
||||
def db_insert(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def load_from_db(self):
|
||||
log_detail = qb.DocType("Bulk Transaction Log Detail")
|
||||
|
||||
@frappe.whitelist()
|
||||
def retry_failing_transaction(log_date=None):
|
||||
if not log_date:
|
||||
log_date = str(date.today())
|
||||
btp = frappe.qb.DocType("Bulk Transaction Log Detail")
|
||||
data = (
|
||||
frappe.qb.from_(btp)
|
||||
.select(btp.transaction_name, btp.from_doctype, btp.to_doctype)
|
||||
.distinct()
|
||||
.where(btp.retried != 1)
|
||||
.where(btp.transaction_status == "Failed")
|
||||
.where(btp.date == log_date)
|
||||
).run(as_dict=True)
|
||||
has_records = frappe.db.sql(
|
||||
f"select exists (select * from `tabBulk Transaction Log Detail` where date = '{self.name}');"
|
||||
)[0][0]
|
||||
if not has_records:
|
||||
raise frappe.DoesNotExistError
|
||||
|
||||
if data:
|
||||
if len(data) > 10:
|
||||
frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
|
||||
else:
|
||||
job(data, log_date)
|
||||
else:
|
||||
return "No Failed Records"
|
||||
succeeded_logs = (
|
||||
qb.from_(log_detail)
|
||||
.select(Count(log_detail.date).as_("count"))
|
||||
.where((log_detail.date == self.name) & (log_detail.transaction_status == "Success"))
|
||||
.run()
|
||||
)[0][0] or 0
|
||||
failed_logs = (
|
||||
qb.from_(log_detail)
|
||||
.select(Count(log_detail.date).as_("count"))
|
||||
.where((log_detail.date == self.name) & (log_detail.transaction_status == "Failed"))
|
||||
.run()
|
||||
)[0][0] or 0
|
||||
total_logs = succeeded_logs + failed_logs
|
||||
transaction_log = frappe._dict(
|
||||
{
|
||||
"date": self.name,
|
||||
"count": total_logs,
|
||||
"succeeded": succeeded_logs,
|
||||
"failed": failed_logs,
|
||||
}
|
||||
)
|
||||
super(Document, self).__init__(serialize_transaction_log(transaction_log))
|
||||
|
||||
@staticmethod
|
||||
def get_list(args):
|
||||
filter_date = parse_list_filters(args)
|
||||
limit = cint(args.get("page_length")) or 20
|
||||
log_detail = qb.DocType("Bulk Transaction Log Detail")
|
||||
|
||||
def job(data, log_date):
|
||||
for d in data:
|
||||
failed = []
|
||||
try:
|
||||
frappe.db.savepoint("before_creation_of_record")
|
||||
task(d.transaction_name, d.from_doctype, d.to_doctype)
|
||||
except Exception as e:
|
||||
frappe.db.rollback(save_point="before_creation_of_record")
|
||||
failed.append(e)
|
||||
update_logger(
|
||||
d.transaction_name,
|
||||
e,
|
||||
d.from_doctype,
|
||||
d.to_doctype,
|
||||
status="Failed",
|
||||
log_date=log_date,
|
||||
restarted=1,
|
||||
dates_query = (
|
||||
qb.from_(log_detail)
|
||||
.select(log_detail.date)
|
||||
.distinct()
|
||||
.orderby(log_detail.date, order=Order.desc)
|
||||
.limit(limit)
|
||||
)
|
||||
if filter_date:
|
||||
dates_query = dates_query.where(log_detail.date == filter_date)
|
||||
dates = dates_query.run()
|
||||
|
||||
transaction_logs = []
|
||||
if dates:
|
||||
transaction_logs_query = (
|
||||
qb.from_(log_detail)
|
||||
.select(log_detail.date.as_("date"), Count(log_detail.date).as_("count"))
|
||||
.where(log_detail.date.isin(dates))
|
||||
.orderby(log_detail.date, order=Order.desc)
|
||||
.groupby(log_detail.date)
|
||||
.limit(limit)
|
||||
)
|
||||
transaction_logs = transaction_logs_query.run(as_dict=True)
|
||||
|
||||
if not failed:
|
||||
update_logger(
|
||||
d.transaction_name,
|
||||
None,
|
||||
d.from_doctype,
|
||||
d.to_doctype,
|
||||
status="Success",
|
||||
log_date=log_date,
|
||||
restarted=1,
|
||||
)
|
||||
return [serialize_transaction_log(x) for x in transaction_logs]
|
||||
|
||||
@staticmethod
|
||||
def get_count(args):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_stats(args):
|
||||
pass
|
||||
|
||||
def db_update(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
pass
|
||||
|
||||
|
||||
def serialize_transaction_log(data):
|
||||
return frappe._dict(
|
||||
name=data.date,
|
||||
date=data.date,
|
||||
log_entries=data.count,
|
||||
succeeded=data.succeeded,
|
||||
failed=data.failed,
|
||||
)
|
||||
|
||||
|
||||
def parse_list_filters(args):
|
||||
# parse date filter
|
||||
filter_date = None
|
||||
for fil in args.get("filters"):
|
||||
if isinstance(fil, list):
|
||||
for elem in fil:
|
||||
if elem == "date":
|
||||
filter_date = fil[3]
|
||||
return filter_date
|
||||
|
||||
@@ -1,79 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.utilities.bulk_transaction import transaction_processing
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBulkTransactionLog(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_company()
|
||||
create_customer()
|
||||
create_item()
|
||||
|
||||
def test_entry_in_log(self):
|
||||
so_name = create_so()
|
||||
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
|
||||
doc = frappe.get_doc("Bulk Transaction Log", str(date.today()))
|
||||
for d in doc.get("logger_data"):
|
||||
if d.transaction_name == so_name:
|
||||
self.assertEqual(d.transaction_name, so_name)
|
||||
self.assertEqual(d.transaction_status, "Success")
|
||||
self.assertEqual(d.from_doctype, "Sales Order")
|
||||
self.assertEqual(d.to_doctype, "Sales Invoice")
|
||||
self.assertEqual(d.retried, 0)
|
||||
|
||||
|
||||
def create_company():
|
||||
if not frappe.db.exists("Company", "_Test Company"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": "_Test Company",
|
||||
"country": "India",
|
||||
"default_currency": "INR",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_customer():
|
||||
if not frappe.db.exists("Customer", "Bulk Customer"):
|
||||
frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert()
|
||||
|
||||
|
||||
def create_item():
|
||||
if not frappe.db.exists("Item", "MK"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item",
|
||||
"item_code": "MK",
|
||||
"item_name": "Milk",
|
||||
"description": "Milk",
|
||||
"item_group": "Products",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_so(intent=None):
|
||||
so = frappe.new_doc("Sales Order")
|
||||
so.customer = "Bulk Customer"
|
||||
so.company = "_Test Company"
|
||||
so.transaction_date = date.today()
|
||||
|
||||
so.set_warehouse = "Finished Goods - _TC"
|
||||
so.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "MK",
|
||||
"delivery_date": date.today(),
|
||||
"qty": 10,
|
||||
"rate": 80,
|
||||
},
|
||||
)
|
||||
so.insert()
|
||||
so.submit()
|
||||
return so.name
|
||||
class TestBulkTransactionLog(FrappeTestCase):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Bulk Transaction Log Detail", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -6,12 +6,12 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"from_doctype",
|
||||
"transaction_name",
|
||||
"date",
|
||||
"time",
|
||||
"transaction_status",
|
||||
"error_description",
|
||||
"from_doctype",
|
||||
"to_doctype",
|
||||
"retried"
|
||||
],
|
||||
@@ -20,8 +20,11 @@
|
||||
"fieldname": "transaction_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Name",
|
||||
"options": "from_doctype"
|
||||
"options": "from_doctype",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_status",
|
||||
@@ -39,9 +42,11 @@
|
||||
{
|
||||
"fieldname": "from_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "From Doctype",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_doctype",
|
||||
@@ -54,8 +59,10 @@
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date ",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time",
|
||||
@@ -66,19 +73,33 @@
|
||||
{
|
||||
"fieldname": "retried",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Retried",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-03 19:57:31.650359",
|
||||
"modified": "2023-11-10 11:44:10.758342",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Bulk Transaction",
|
||||
"name": "Bulk Transaction Log Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBulkTransactionLogDetail(FrappeTestCase):
|
||||
pass
|
||||
@@ -16,7 +16,7 @@
|
||||
"transaction_settings_section",
|
||||
"po_required",
|
||||
"pr_required",
|
||||
"over_order_allowance",
|
||||
"blanket_order_allowance",
|
||||
"column_break_12",
|
||||
"maintain_same_rate",
|
||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||
@@ -159,19 +159,19 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Landed Cost Based on Purchase Invoice Rate"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
|
||||
"fieldname": "over_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Over Order Allowance (%)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
|
||||
"fieldname": "use_transaction_date_exchange_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Transaction Date Exchange Rate"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Percentage you are allowed to order beyond the Blanket Order quantity.",
|
||||
"fieldname": "blanket_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Blanket Order Allowance (%)"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -179,7 +179,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-16 16:22:03.201078",
|
||||
"modified": "2023-10-25 14:03:32.520418",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -556,6 +556,9 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"bom": "bom",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item",
|
||||
"sales_order": "sales_order",
|
||||
"sales_order_item": "sales_order_item",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
@@ -632,6 +635,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
"field_map": {
|
||||
"name": "po_detail",
|
||||
"parent": "purchase_order",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
"billed_amt",
|
||||
"accounting_details",
|
||||
"expense_account",
|
||||
"column_break_fyqr",
|
||||
"wip_composite_asset",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"manufacturer_part_no",
|
||||
@@ -187,6 +189,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -468,6 +471,7 @@
|
||||
"fieldname": "material_request",
|
||||
"fieldtype": "Link",
|
||||
"label": "Material Request",
|
||||
"mandatory_depends_on": "eval: doc.material_request_item",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "prevdoc_docname",
|
||||
"oldfieldtype": "Link",
|
||||
@@ -483,6 +487,7 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Material Request Item",
|
||||
"mandatory_depends_on": "eval: doc.material_request",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "prevdoc_detail_docname",
|
||||
"oldfieldtype": "Data",
|
||||
@@ -896,13 +901,23 @@
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_composite_asset",
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fyqr",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-13 16:22:40.825092",
|
||||
"modified": "2023-11-14 18:34:27.267382",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
@@ -915,4 +930,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -260,13 +261,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-24 17:26:46.276934",
|
||||
"modified": "2023-11-14 18:34:48.327224",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@
|
||||
"fieldname": "supplier_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Supplier Type",
|
||||
"options": "Company\nIndividual",
|
||||
"options": "Company\nIndividual\nProprietorship\nPartnership",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -485,7 +485,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2023-09-25 12:48:21.869563",
|
||||
"modified": "2023-10-19 16:55:15.148325",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -559,13 +560,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-19 12:36:26.913211",
|
||||
"modified": "2023-11-14 18:35:03.435817",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -44,11 +44,6 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
filters: { "disabled": 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2267,6 +2267,7 @@ class AccountsController(TransactionBase):
|
||||
repost_ledger = frappe.new_doc("Repost Accounting Ledger")
|
||||
repost_ledger.company = self.company
|
||||
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
|
||||
repost_ledger.flags.ignore_permissions = True
|
||||
repost_ledger.insert()
|
||||
repost_ledger.submit()
|
||||
self.db_set("repost_required", 0)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import ValidationError, _, msgprint
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.contacts.doctype.address.address import render_address
|
||||
from frappe.utils import cint, flt, getdate
|
||||
from frappe.utils.data import nowtime
|
||||
|
||||
@@ -105,26 +105,26 @@ class BuyingController(SubcontractingController):
|
||||
def set_rate_for_standalone_debit_note(self):
|
||||
if self.get("is_return") and self.get("update_stock") and not self.return_against:
|
||||
for row in self.items:
|
||||
if row.rate <= 0:
|
||||
# override the rate with valuation rate
|
||||
row.rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
"posting_date": self.get("posting_date"),
|
||||
"posting_time": self.get("posting_time"),
|
||||
"qty": row.qty,
|
||||
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
# override the rate with valuation rate
|
||||
row.rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
"posting_date": self.get("posting_date"),
|
||||
"posting_time": self.get("posting_time"),
|
||||
"qty": row.qty,
|
||||
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
row.discount_percentage = 0.0
|
||||
row.discount_amount = 0.0
|
||||
row.margin_rate_or_amount = 0.0
|
||||
row.discount_percentage = 0.0
|
||||
row.discount_amount = 0.0
|
||||
row.margin_rate_or_amount = 0.0
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
super(BuyingController, self).set_missing_values(for_validate)
|
||||
@@ -246,7 +246,9 @@ class BuyingController(SubcontractingController):
|
||||
|
||||
for address_field, address_display_field in address_dict.items():
|
||||
if self.get(address_field):
|
||||
self.set(address_display_field, get_address_display(self.get(address_field)))
|
||||
self.set(
|
||||
address_display_field, render_address(self.get(address_field), check_permissions=False)
|
||||
)
|
||||
|
||||
def set_total_in_words(self):
|
||||
from frappe.utils import money_in_words
|
||||
@@ -363,7 +365,7 @@ class BuyingController(SubcontractingController):
|
||||
{
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.get("from_warehouse"),
|
||||
"posting_date": self.get("posting_date") or self.get("transation_date"),
|
||||
"posting_date": self.get("posting_date") or self.get("transaction_date"),
|
||||
"posting_time": posting_time,
|
||||
"qty": -1 * flt(d.get("stock_qty")),
|
||||
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
|
||||
|
||||
@@ -611,6 +611,8 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
|
||||
condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}"
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
|
||||
@@ -47,15 +47,15 @@ status_map = {
|
||||
],
|
||||
[
|
||||
"To Bill",
|
||||
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
|
||||
"eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
|
||||
],
|
||||
[
|
||||
"To Deliver",
|
||||
"eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note",
|
||||
"eval:self.per_delivered < 100 and self.per_billed >= 100 and self.docstatus == 1 and not self.skip_delivery_note",
|
||||
],
|
||||
[
|
||||
"Completed",
|
||||
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
|
||||
"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"],
|
||||
|
||||
@@ -62,9 +62,12 @@ class StockController(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
|
||||
|
||||
if (
|
||||
cint(erpnext.is_perpetual_inventory_enabled(self.company))
|
||||
or provisional_accounting_for_non_stock_items
|
||||
or is_asset_pr
|
||||
):
|
||||
warehouse_account = get_warehouse_account_map(self.company)
|
||||
|
||||
@@ -73,11 +76,6 @@ class StockController(AccountsController):
|
||||
gl_entries = self.get_gl_entries(warehouse_account)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
|
||||
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
|
||||
gl_entries = []
|
||||
gl_entries = self.get_asset_gl_entry(gl_entries)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
|
||||
def validate_serialized_batch(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
@@ -692,13 +690,21 @@ class StockController(AccountsController):
|
||||
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
|
||||
|
||||
def validate_internal_transfer(self):
|
||||
if (
|
||||
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
|
||||
and self.is_internal_transfer()
|
||||
):
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"):
|
||||
if self.is_internal_transfer():
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
else:
|
||||
self.validate_internal_transfer_warehouse()
|
||||
|
||||
def validate_internal_transfer_warehouse(self):
|
||||
for row in self.items:
|
||||
if row.get("target_warehouse"):
|
||||
row.target_warehouse = None
|
||||
|
||||
if row.get("from_warehouse"):
|
||||
row.from_warehouse = None
|
||||
|
||||
def validate_in_transit_warehouses(self):
|
||||
if (
|
||||
@@ -855,8 +861,9 @@ class StockController(AccountsController):
|
||||
|
||||
@frappe.whitelist()
|
||||
def show_accounting_ledger_preview(company, doctype, docname):
|
||||
filters = {"company": company, "include_dimensions": 1}
|
||||
filters = frappe._dict(company=company, include_dimensions=1)
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
doc.run_method("before_gl_preview")
|
||||
|
||||
gl_columns, gl_data = get_accounting_ledger_preview(doc, filters)
|
||||
|
||||
@@ -867,8 +874,9 @@ def show_accounting_ledger_preview(company, doctype, docname):
|
||||
|
||||
@frappe.whitelist()
|
||||
def show_stock_ledger_preview(company, doctype, docname):
|
||||
filters = {"company": company}
|
||||
filters = frappe._dict(company=company)
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
doc.run_method("before_sl_preview")
|
||||
|
||||
sl_columns, sl_data = get_stock_ledger_preview(doc, filters)
|
||||
|
||||
@@ -1202,8 +1210,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa
|
||||
|
||||
repost_entry = frappe.new_doc("Repost Item Valuation")
|
||||
repost_entry.based_on = "Item and Warehouse"
|
||||
repost_entry.voucher_type = voucher_type
|
||||
repost_entry.voucher_no = voucher_no
|
||||
|
||||
repost_entry.item_code = sle.item_code
|
||||
repost_entry.warehouse = sle.warehouse
|
||||
|
||||
@@ -626,6 +626,18 @@ class SubcontractingController(StockController):
|
||||
(row.item_code, row.get(self.subcontract_data.order_field))
|
||||
] -= row.qty
|
||||
|
||||
def __set_rate_for_serial_and_batch_bundle(self):
|
||||
if self.doctype != "Subcontracting Receipt":
|
||||
return
|
||||
|
||||
for row in self.get(self.raw_material_table):
|
||||
if not row.get("serial_and_batch_bundle"):
|
||||
continue
|
||||
|
||||
row.rate = frappe.get_cached_value(
|
||||
"Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"
|
||||
)
|
||||
|
||||
def __modify_serial_and_batch_bundle(self):
|
||||
if self.is_new():
|
||||
return
|
||||
@@ -681,6 +693,7 @@ class SubcontractingController(StockController):
|
||||
self.__remove_changed_rows()
|
||||
self.__set_supplied_items()
|
||||
self.__modify_serial_and_batch_bundle()
|
||||
self.__set_rate_for_serial_and_batch_bundle()
|
||||
|
||||
def __validate_batch_no(self, row, key):
|
||||
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -165,7 +166,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-30 16:39:09.775720",
|
||||
"modified": "2023-11-14 18:35:30.887278",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity Item",
|
||||
@@ -173,5 +174,6 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.doctype.tag.tag import add_tag
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_months, formatdate, getdate, today
|
||||
from frappe.utils import add_months, formatdate, getdate, sbool, today
|
||||
from plaid.errors import ItemError
|
||||
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
@@ -237,8 +237,6 @@ def new_bank_transaction(transaction):
|
||||
deposit = abs(amount)
|
||||
withdrawal = 0.0
|
||||
|
||||
status = "Pending" if transaction["pending"] == True else "Settled"
|
||||
|
||||
tags = []
|
||||
if transaction["category"]:
|
||||
try:
|
||||
@@ -247,13 +245,14 @@ def new_bank_transaction(transaction):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not frappe.db.exists("Bank Transaction", dict(transaction_id=transaction["transaction_id"])):
|
||||
if not frappe.db.exists(
|
||||
"Bank Transaction", dict(transaction_id=transaction["transaction_id"])
|
||||
) and not sbool(transaction["pending"]):
|
||||
try:
|
||||
new_transaction = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Bank Transaction",
|
||||
"date": getdate(transaction["date"]),
|
||||
"status": status,
|
||||
"bank_account": bank_account,
|
||||
"deposit": deposit,
|
||||
"withdrawal": withdrawal,
|
||||
|
||||
@@ -107,7 +107,7 @@ def validate_against_blanket_order(order_doc):
|
||||
allowance = flt(
|
||||
frappe.db.get_single_value(
|
||||
"Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings",
|
||||
"over_order_allowance",
|
||||
"blanket_order_allowance",
|
||||
)
|
||||
)
|
||||
for bo_name, item_data in order_data.items():
|
||||
|
||||
@@ -63,7 +63,7 @@ class TestBlanketOrder(FrappeTestCase):
|
||||
po1.currency = get_company_currency(po1.company)
|
||||
self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
|
||||
|
||||
def test_over_order_allowance(self):
|
||||
def test_blanket_order_allowance(self):
|
||||
# Sales Order
|
||||
bo = make_blanket_order(blanket_order_type="Selling", quantity=100)
|
||||
|
||||
@@ -74,7 +74,7 @@ class TestBlanketOrder(FrappeTestCase):
|
||||
so.items[0].qty = 110
|
||||
self.assertRaises(frappe.ValidationError, so.submit)
|
||||
|
||||
frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10)
|
||||
frappe.db.set_single_value("Selling Settings", "blanket_order_allowance", 10)
|
||||
so.submit()
|
||||
|
||||
# Purchase Order
|
||||
@@ -87,7 +87,7 @@ class TestBlanketOrder(FrappeTestCase):
|
||||
po.items[0].qty = 110
|
||||
self.assertRaises(frappe.ValidationError, po.submit)
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10)
|
||||
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
|
||||
po.submit()
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user