mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-25 13:45:04 +00:00
Compare commits
278 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47d37aa628 | ||
|
|
6260241c55 | ||
|
|
6871331ff3 | ||
|
|
bb2aa904b2 | ||
|
|
e490bd7785 | ||
|
|
ca6a78882c | ||
|
|
321ac4c5f0 | ||
|
|
3929cc30ca | ||
|
|
25f67923ea | ||
|
|
5dc7faba0c | ||
|
|
2aa9171027 | ||
|
|
730a3bb7ce | ||
|
|
e9a2b002d8 | ||
|
|
faf025454f | ||
|
|
50f3b01eb7 | ||
|
|
f777d24679 | ||
|
|
d72fbe9125 | ||
|
|
3ad6d0890a | ||
|
|
776c884403 | ||
|
|
a4b5fc8100 | ||
|
|
b0547b5619 | ||
|
|
9cf8955d24 | ||
|
|
f7c820ffad | ||
|
|
1773e31499 | ||
|
|
466d0849e8 | ||
|
|
28b6afcb10 | ||
|
|
bdde30a456 | ||
|
|
aea169d4d4 | ||
|
|
b0aa4efd3d | ||
|
|
7dce68bdcc | ||
|
|
436ba6f244 | ||
|
|
79ded4bf19 | ||
|
|
5af23e760c | ||
|
|
7601861f21 | ||
|
|
1d1af26f7a | ||
|
|
47093bebd3 | ||
|
|
205d971253 | ||
|
|
49a787b1a8 | ||
|
|
a88d322e4f | ||
|
|
dfdaad0111 | ||
|
|
d80e8c6b15 | ||
|
|
d7d5317b0e | ||
|
|
445e9c9041 | ||
|
|
e96ef7338e | ||
|
|
ae960e735a | ||
|
|
fd19ac55e3 | ||
|
|
8a3a0688ba | ||
|
|
8d5efc901d | ||
|
|
5dcd806176 | ||
|
|
0448d56a4f | ||
|
|
f74132b0be | ||
|
|
6353deacf0 | ||
|
|
756f08b9ba | ||
|
|
2293696ab4 | ||
|
|
06c6da5c0d | ||
|
|
5261aba81f | ||
|
|
f170cb9214 | ||
|
|
8055773425 | ||
|
|
82fa58d4e3 | ||
|
|
57c6d616e6 | ||
|
|
639512a7c2 | ||
|
|
ac13e7362c | ||
|
|
1e7dfd3071 | ||
|
|
e7183e3ea9 | ||
|
|
c4e57040b7 | ||
|
|
c1a7d614a2 | ||
|
|
7392f9c662 | ||
|
|
aa6ef76043 | ||
|
|
f6f5ff0be9 | ||
|
|
00def82843 | ||
|
|
e08f1145c9 | ||
|
|
ddf82cf433 | ||
|
|
59a5e132fe | ||
|
|
7fc7105c48 | ||
|
|
3daf6f822a | ||
|
|
d7278477d6 | ||
|
|
d0571530eb | ||
|
|
392ee2e0fd | ||
|
|
562a55122b | ||
|
|
b226adeb2f | ||
|
|
12f027b3f4 | ||
|
|
9f8aa7c59d | ||
|
|
40f904391e | ||
|
|
bfb85527dc | ||
|
|
b868e2bd5b | ||
|
|
b7e74b479f | ||
|
|
7c92ecdc9d | ||
|
|
80bc235d6e | ||
|
|
883eaee014 | ||
|
|
ae319760a0 | ||
|
|
4e2d0aa892 | ||
|
|
5afb8b59c9 | ||
|
|
8136657dfc | ||
|
|
9b5185adc9 | ||
|
|
296433a1dd | ||
|
|
5033f189f9 | ||
|
|
d85185db2e | ||
|
|
76b6e0407d | ||
|
|
2fdc575441 | ||
|
|
7b9afa43ff | ||
|
|
49735bc120 | ||
|
|
a70696ea77 | ||
|
|
2849e0daed | ||
|
|
da27c0fb2d | ||
|
|
430980a836 | ||
|
|
51e33e1556 | ||
|
|
8d4a19cecf | ||
|
|
9a34518e54 | ||
|
|
39841f9edd | ||
|
|
1ccd5e4ff5 | ||
|
|
05d514ca8e | ||
|
|
c7c751ecd0 | ||
|
|
fa1f623801 | ||
|
|
8dbe753a25 | ||
|
|
a060000b70 | ||
|
|
b615cfc540 | ||
|
|
32b6eed691 | ||
|
|
ad03008a1a | ||
|
|
2ec1a2f862 | ||
|
|
f5f48f7480 | ||
|
|
8dbf2ced79 | ||
|
|
b951dbfced | ||
|
|
863a200989 | ||
|
|
8dafe103bd | ||
|
|
eda9800c93 | ||
|
|
6d983b5f45 | ||
|
|
65614497ed | ||
|
|
785bec98d3 | ||
|
|
6b08aa2198 | ||
|
|
dd76695d3a | ||
|
|
236a064cd3 | ||
|
|
dd685e50df | ||
|
|
85853295c4 | ||
|
|
4eb37cce26 | ||
|
|
d0698b32bb | ||
|
|
bc01007c16 | ||
|
|
fb2ca3e172 | ||
|
|
7d66cb2195 | ||
|
|
58cb4303ee | ||
|
|
56c70d300b | ||
|
|
1f16c47a2c | ||
|
|
7b14e9d5f6 | ||
|
|
966adfc081 | ||
|
|
d1a8202624 | ||
|
|
7a2df24129 | ||
|
|
ecb116aa99 | ||
|
|
8446e87cc2 | ||
|
|
fb19e7f15e | ||
|
|
c840f30a20 | ||
|
|
0813f02ca5 | ||
|
|
a7e2867526 | ||
|
|
077f3928cf | ||
|
|
c5528844fd | ||
|
|
55885e84ab | ||
|
|
2faf2ce468 | ||
|
|
7ab18fc434 | ||
|
|
a104a4bca6 | ||
|
|
6979c3e490 | ||
|
|
57ce73d540 | ||
|
|
424ebd154a | ||
|
|
7c7dc5328e | ||
|
|
cb655226de | ||
|
|
010ccf80f5 | ||
|
|
13ea910fa6 | ||
|
|
9c0d80cef4 | ||
|
|
620f4f2c10 | ||
|
|
89695022c7 | ||
|
|
50627733b4 | ||
|
|
7942ad488e | ||
|
|
c3d7348c73 | ||
|
|
ad178db28f | ||
|
|
984703c3c9 | ||
|
|
6b34568128 | ||
|
|
cf0d936195 | ||
|
|
0eab723243 | ||
|
|
935286ff94 | ||
|
|
9a2674ea29 | ||
|
|
0405aae4a0 | ||
|
|
db29180eec | ||
|
|
b5bed65d9e | ||
|
|
3f7f5bd8f0 | ||
|
|
3f433b1b8b | ||
|
|
0532f72cb1 | ||
|
|
0ac78a96a9 | ||
|
|
043dc1b14b | ||
|
|
c5a6b17c15 | ||
|
|
16c3f3bcbd | ||
|
|
4d804951c6 | ||
|
|
c5ddb4153a | ||
|
|
b0ed3c2f0a | ||
|
|
3f8d58c9c6 | ||
|
|
8a3139f6f5 | ||
|
|
0b601d8cae | ||
|
|
9be71e45b9 | ||
|
|
e58604a90f | ||
|
|
e895bb3a63 | ||
|
|
38a5e6f10b | ||
|
|
8f0cf1b7dc | ||
|
|
3da2597703 | ||
|
|
82bf726b83 | ||
|
|
7d4dc67b61 | ||
|
|
35f5498913 | ||
|
|
ed557274d2 | ||
|
|
18e6931d78 | ||
|
|
560a45b343 | ||
|
|
09cde5da08 | ||
|
|
503c0f33bb | ||
|
|
38184f77c7 | ||
|
|
449b641560 | ||
|
|
3cc5e5591d | ||
|
|
bc5e01e846 | ||
|
|
d54e756453 | ||
|
|
3d72ec7ca4 | ||
|
|
c53d281b9e | ||
|
|
94b38b900c | ||
|
|
235f1664e0 | ||
|
|
24da29a817 | ||
|
|
64658fc790 | ||
|
|
c87012046d | ||
|
|
21ad1dba19 | ||
|
|
9c356a0ac2 | ||
|
|
930aee1d70 | ||
|
|
4de97a4fcb | ||
|
|
932d1e7c62 | ||
|
|
a8b26ebc6b | ||
|
|
71ad448281 | ||
|
|
6eb867a1e4 | ||
|
|
5e1a89b3f9 | ||
|
|
b2b8b72952 | ||
|
|
555ebc190c | ||
|
|
05fed9e352 | ||
|
|
eb3630bcf8 | ||
|
|
c2001bec15 | ||
|
|
be925d2d33 | ||
|
|
b0a184e937 | ||
|
|
21b430a575 | ||
|
|
65dfef021a | ||
|
|
86c6ae6275 | ||
|
|
fed25fac76 | ||
|
|
6b5cffc77a | ||
|
|
ad64065ec6 | ||
|
|
1a3dae0f9a | ||
|
|
15308c9aac | ||
|
|
09ba6c1266 | ||
|
|
afa6ed9e22 | ||
|
|
3d2ff7eea3 | ||
|
|
ee5898a773 | ||
|
|
2c93f81d59 | ||
|
|
e19cade12d | ||
|
|
273cc35b91 | ||
|
|
69b569df46 | ||
|
|
a07d58bd0e | ||
|
|
50fdc9dda0 | ||
|
|
9b0a46853f | ||
|
|
64d7590bd0 | ||
|
|
e8a706a5ea | ||
|
|
289236d065 | ||
|
|
ef4471f8c0 | ||
|
|
e616a0bb63 | ||
|
|
174b76f6ff | ||
|
|
875d7f8b72 | ||
|
|
2c1fdecc91 | ||
|
|
4d2169e48e | ||
|
|
b75aae99ca | ||
|
|
f502ec2268 | ||
|
|
0196d7ac23 | ||
|
|
357697702b | ||
|
|
4a225b069b | ||
|
|
28c9458482 | ||
|
|
d8e743013f | ||
|
|
555cc62e50 | ||
|
|
7df4009dd3 | ||
|
|
08a9a9d188 | ||
|
|
e98be384f9 | ||
|
|
fdf2977ce2 | ||
|
|
3276f39740 | ||
|
|
0b1da08d78 | ||
|
|
ffdc11bbf6 |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.46.3"
|
||||
__version__ = "14.51.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -68,7 +68,12 @@
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching",
|
||||
"tab_break_dpet",
|
||||
"show_balance_in_coa"
|
||||
"show_balance_in_coa",
|
||||
"reports_tab",
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
"column_break_lvjk",
|
||||
"receivable_payable_remarks_length"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -429,6 +434,34 @@
|
||||
"fieldname": "show_balance_in_coa",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Balances in Chart Of Accounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "reports_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Reports"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Truncates 'Remarks' column to set character length",
|
||||
"fieldname": "general_ledger_remarks_length",
|
||||
"fieldtype": "Int",
|
||||
"label": "General Ledger"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Truncates 'Remarks' column to set character length",
|
||||
"fieldname": "receivable_payable_remarks_length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Accounts Receivable/Payable"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lvjk",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "remarks_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Remarks Column Length"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -436,7 +469,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-27 15:05:34.000264",
|
||||
"modified": "2023-11-20 09:37:47.650347",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -423,7 +423,9 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
vouchers = json.loads(vouchers)
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
transaction.add_payment_entries(vouchers)
|
||||
return frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
transaction.save()
|
||||
|
||||
return transaction
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -112,7 +112,8 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
for party in parties:
|
||||
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
||||
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
|
||||
field = party.lower() + "_name"
|
||||
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
|
||||
|
||||
for field in ["bank_party_name", "description"]:
|
||||
if not self.get(field):
|
||||
@@ -131,7 +132,11 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||
skip = False
|
||||
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
||||
result = process.extract(
|
||||
query=self.get(field),
|
||||
choices={row.get("name"): row.get("party_name") for row in names},
|
||||
scorer=fuzz.token_set_ratio,
|
||||
)
|
||||
party_name, skip = self.process_fuzzy_result(result)
|
||||
|
||||
if not party_name:
|
||||
@@ -149,14 +154,14 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
Returns: Result, Skip (whether or not to discontinue matching)
|
||||
"""
|
||||
PARTY, SCORE, CUTOFF = 0, 1, 80
|
||||
SCORE, PARTY_ID, CUTOFF = 1, 2, 80
|
||||
|
||||
if not result or not len(result):
|
||||
return None, False
|
||||
|
||||
first_result = result[0]
|
||||
if len(result) == 1:
|
||||
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
|
||||
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
|
||||
|
||||
second_result = result[1]
|
||||
if first_result[SCORE] > CUTOFF:
|
||||
@@ -165,7 +170,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
if first_result[SCORE] == second_result[SCORE]:
|
||||
return None, True
|
||||
|
||||
return first_result[PARTY], True
|
||||
return first_result[PARTY_ID], True
|
||||
else:
|
||||
return None, False
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"status",
|
||||
"bank_account",
|
||||
"company",
|
||||
"amended_from",
|
||||
"section_break_4",
|
||||
"deposit",
|
||||
"withdrawal",
|
||||
@@ -25,10 +26,10 @@
|
||||
"transaction_id",
|
||||
"transaction_type",
|
||||
"section_break_14",
|
||||
"column_break_oufv",
|
||||
"payment_entries",
|
||||
"section_break_18",
|
||||
"allocated_amount",
|
||||
"amended_from",
|
||||
"column_break_17",
|
||||
"unallocated_amount",
|
||||
"party_section",
|
||||
@@ -138,10 +139,12 @@
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "currency"
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@@ -157,10 +160,12 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "unallocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Unallocated Amount",
|
||||
"options": "currency"
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "party_section",
|
||||
@@ -225,11 +230,15 @@
|
||||
"fieldname": "bank_party_account_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Party Account No. (Bank Statement)"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_oufv",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-06 13:58:12.821411",
|
||||
"modified": "2023-11-18 18:32:47.203694",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Transaction",
|
||||
|
||||
@@ -2,78 +2,73 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
|
||||
class BankTransaction(StatusUpdater):
|
||||
def after_insert(self):
|
||||
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit))
|
||||
def before_validate(self):
|
||||
self.update_allocated_amount()
|
||||
|
||||
def on_submit(self):
|
||||
self.clear_linked_payment_entries()
|
||||
def validate(self):
|
||||
self.validate_duplicate_references()
|
||||
|
||||
def validate_duplicate_references(self):
|
||||
"""Make sure the same voucher is not allocated twice within the same Bank Transaction"""
|
||||
if not self.payment_entries:
|
||||
return
|
||||
|
||||
pe = []
|
||||
for row in self.payment_entries:
|
||||
reference = (row.payment_document, row.payment_entry)
|
||||
if reference in pe:
|
||||
frappe.throw(
|
||||
_("{0} {1} is allocated twice in this Bank Transaction").format(
|
||||
row.payment_document, row.payment_entry
|
||||
)
|
||||
)
|
||||
pe.append(reference)
|
||||
|
||||
def update_allocated_amount(self):
|
||||
self.allocated_amount = (
|
||||
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
|
||||
)
|
||||
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
|
||||
|
||||
def before_submit(self):
|
||||
self.allocate_payment_entries()
|
||||
self.set_status()
|
||||
|
||||
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
|
||||
self.auto_set_party()
|
||||
|
||||
_saving_flag = False
|
||||
|
||||
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
|
||||
def on_update_after_submit(self):
|
||||
"Run on save(). Avoid recursion caused by multiple saves"
|
||||
if not self._saving_flag:
|
||||
self._saving_flag = True
|
||||
self.clear_linked_payment_entries()
|
||||
self.update_allocations()
|
||||
self._saving_flag = False
|
||||
def before_update_after_submit(self):
|
||||
self.validate_duplicate_references()
|
||||
self.allocate_payment_entries()
|
||||
self.update_allocated_amount()
|
||||
|
||||
def on_cancel(self):
|
||||
self.clear_linked_payment_entries(for_cancel=True)
|
||||
self.set_status(update=True)
|
||||
for payment_entry in self.payment_entries:
|
||||
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
|
||||
|
||||
def update_allocations(self):
|
||||
"The doctype does not allow modifications after submission, so write to the db direct"
|
||||
if self.payment_entries:
|
||||
allocated_amount = sum(p.allocated_amount for p in self.payment_entries)
|
||||
else:
|
||||
allocated_amount = 0.0
|
||||
|
||||
amount = abs(flt(self.withdrawal) - flt(self.deposit))
|
||||
self.db_set("allocated_amount", flt(allocated_amount))
|
||||
self.db_set("unallocated_amount", amount - flt(allocated_amount))
|
||||
self.reload()
|
||||
self.set_status(update=True)
|
||||
|
||||
def add_payment_entries(self, vouchers):
|
||||
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
|
||||
if 0.0 >= self.unallocated_amount:
|
||||
frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name))
|
||||
frappe.throw(_("Bank Transaction {0} is already fully reconciled").format(self.name))
|
||||
|
||||
added = False
|
||||
for voucher in vouchers:
|
||||
# Can't add same voucher twice
|
||||
found = False
|
||||
for pe in self.payment_entries:
|
||||
if (
|
||||
pe.payment_document == voucher["payment_doctype"]
|
||||
and pe.payment_entry == voucher["payment_name"]
|
||||
):
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
pe = {
|
||||
self.append(
|
||||
"payment_entries",
|
||||
{
|
||||
"payment_document": voucher["payment_doctype"],
|
||||
"payment_entry": voucher["payment_name"],
|
||||
"allocated_amount": 0.0, # Temporary
|
||||
}
|
||||
child = self.append("payment_entries", pe)
|
||||
added = True
|
||||
|
||||
# runs on_update_after_submit
|
||||
if added:
|
||||
self.save()
|
||||
},
|
||||
)
|
||||
|
||||
def allocate_payment_entries(self):
|
||||
"""Refactored from bank reconciliation tool.
|
||||
@@ -89,8 +84,8 @@ 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
|
||||
to_remove = []
|
||||
for payment_entry in self.payment_entries:
|
||||
if payment_entry.allocated_amount == 0.0:
|
||||
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
|
||||
@@ -100,49 +95,39 @@ class BankTransaction(StatusUpdater):
|
||||
if 0.0 == unallocated_amount:
|
||||
if should_clear:
|
||||
latest_transaction.clear_linked_payment_entry(payment_entry)
|
||||
self.db_delete_payment_entry(payment_entry)
|
||||
to_remove.append(payment_entry)
|
||||
|
||||
elif remaining_amount <= 0.0:
|
||||
self.db_delete_payment_entry(payment_entry)
|
||||
to_remove.append(payment_entry)
|
||||
|
||||
elif 0.0 < unallocated_amount and unallocated_amount <= remaining_amount:
|
||||
payment_entry.db_set("allocated_amount", unallocated_amount)
|
||||
elif 0.0 < unallocated_amount <= remaining_amount:
|
||||
payment_entry.allocated_amount = unallocated_amount
|
||||
remaining_amount -= unallocated_amount
|
||||
if should_clear:
|
||||
latest_transaction.clear_linked_payment_entry(payment_entry)
|
||||
|
||||
elif 0.0 < unallocated_amount and unallocated_amount > remaining_amount:
|
||||
payment_entry.db_set("allocated_amount", remaining_amount)
|
||||
elif 0.0 < unallocated_amount:
|
||||
payment_entry.allocated_amount = remaining_amount
|
||||
remaining_amount = 0.0
|
||||
|
||||
elif 0.0 > unallocated_amount:
|
||||
self.db_delete_payment_entry(payment_entry)
|
||||
frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
|
||||
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
|
||||
|
||||
self.reload()
|
||||
|
||||
def db_delete_payment_entry(self, payment_entry):
|
||||
frappe.db.delete("Bank Transaction Payments", {"name": payment_entry.name})
|
||||
for payment_entry in to_remove:
|
||||
self.remove(to_remove)
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_payment_entries(self):
|
||||
for payment_entry in self.payment_entries:
|
||||
self.remove_payment_entry(payment_entry)
|
||||
# runs on_update_after_submit
|
||||
self.save()
|
||||
|
||||
self.save() # runs before_update_after_submit
|
||||
|
||||
def remove_payment_entry(self, payment_entry):
|
||||
"Clear payment entry and clearance"
|
||||
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
|
||||
self.remove(payment_entry)
|
||||
|
||||
def clear_linked_payment_entries(self, for_cancel=False):
|
||||
if for_cancel:
|
||||
for payment_entry in self.payment_entries:
|
||||
self.clear_linked_payment_entry(payment_entry, for_cancel)
|
||||
else:
|
||||
self.allocate_payment_entries()
|
||||
|
||||
def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
|
||||
clearance_date = None if for_cancel else self.date
|
||||
set_voucher_clearance(
|
||||
@@ -163,11 +148,10 @@ class BankTransaction(StatusUpdater):
|
||||
deposit=self.deposit,
|
||||
).match()
|
||||
|
||||
if result:
|
||||
party_type, party = result
|
||||
frappe.db.set_value(
|
||||
"Bank Transaction", self.name, field={"party_type": party_type, "party": party}
|
||||
)
|
||||
if not result:
|
||||
return
|
||||
|
||||
self.party_type, self.party = result
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -199,9 +183,7 @@ def get_clearance_details(transaction, payment_entry):
|
||||
if gle["gl_account"] == gl_bank_account:
|
||||
if gle["amount"] <= 0.0:
|
||||
frappe.throw(
|
||||
frappe._("Voucher {0} value is broken: {1}").format(
|
||||
payment_entry.payment_entry, gle["amount"]
|
||||
)
|
||||
_("Voucher {0} value is broken: {1}").format(payment_entry.payment_entry, gle["amount"])
|
||||
)
|
||||
|
||||
unmatched_gles -= 1
|
||||
@@ -222,7 +204,7 @@ def get_clearance_details(transaction, payment_entry):
|
||||
|
||||
def get_related_bank_gl_entries(doctype, docname):
|
||||
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
|
||||
result = frappe.db.sql(
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
|
||||
@@ -240,7 +222,6 @@ def get_related_bank_gl_entries(doctype, docname):
|
||||
dict(doctype=doctype, docname=docname),
|
||||
as_dict=True,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def get_total_allocated_amount(doctype, docname):
|
||||
@@ -373,6 +354,7 @@ def set_voucher_clearance(doctype, docname, clearance_date, self):
|
||||
if clearance_date:
|
||||
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
|
||||
bt.add_payment_entries(vouchers)
|
||||
bt.save()
|
||||
else:
|
||||
for pe in bt.payment_entries:
|
||||
if pe.payment_document == self.doctype and pe.payment_entry == self.name:
|
||||
|
||||
@@ -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
|
||||
@@ -112,7 +113,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
|
||||
if as_dict:
|
||||
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
|
||||
else:
|
||||
if not row[1]:
|
||||
if not row[1] and len(row) > 1:
|
||||
row[1] = row[0]
|
||||
row[3] = row[2]
|
||||
data.append(row)
|
||||
@@ -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
|
||||
|
@@ -51,7 +51,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
}, __('Make'));
|
||||
}
|
||||
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||
},
|
||||
before_save: function(frm) {
|
||||
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
|
||||
|
||||
@@ -548,8 +548,16 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-10 14:32:22.366895",
|
||||
"links": [
|
||||
{
|
||||
"is_child_table": 1,
|
||||
"link_doctype": "Bank Transaction Payments",
|
||||
"link_fieldname": "payment_entry",
|
||||
"parent_doctype": "Bank Transaction",
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-23 12:11:04.128015",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -496,7 +496,7 @@ class JournalEntry(AccountsController):
|
||||
).format(d.reference_name, d.account)
|
||||
)
|
||||
else:
|
||||
dr_or_cr = "debit" if d.credit > 0 else "credit"
|
||||
dr_or_cr = "debit" if flt(d.credit) > 0 else "credit"
|
||||
valid = False
|
||||
for jvd in against_entries:
|
||||
if flt(jvd[dr_or_cr]) > 0:
|
||||
|
||||
@@ -203,7 +203,8 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Reference Type",
|
||||
"no_copy": 1,
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry"
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
@@ -211,7 +212,8 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_type"
|
||||
"options": "reference_type",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
||||
@@ -278,13 +280,14 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Detail No",
|
||||
"no_copy": 1
|
||||
"no_copy": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-16 14:11:13.507807",
|
||||
"modified": "2023-11-23 11:44:25.841187",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
||||
@@ -7,7 +7,7 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
||||
|
||||
frappe.ui.form.on('Payment Entry', {
|
||||
onload: function(frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries'];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
|
||||
|
||||
if(frm.doc.__islocal) {
|
||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||
@@ -152,13 +152,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
frm.events.show_general_ledger(frm);
|
||||
if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) {
|
||||
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'));
|
||||
|
||||
}
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||
},
|
||||
|
||||
validate_company: (frm) => {
|
||||
@@ -829,7 +829,6 @@ frappe.ui.form.on('Payment Entry', {
|
||||
else
|
||||
total_negative_outstanding += Math.abs(flt(row.outstanding_amount));
|
||||
})
|
||||
|
||||
var allocated_negative_outstanding = 0;
|
||||
if (
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||
@@ -844,6 +843,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"))
|
||||
if(paid_amount > total_negative_outstanding) {
|
||||
if(total_negative_outstanding == 0) {
|
||||
frappe.msgprint(
|
||||
|
||||
@@ -739,8 +739,16 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-19 11:38:04.387219",
|
||||
"links": [
|
||||
{
|
||||
"is_child_table": 1,
|
||||
"link_doctype": "Bank Transaction Payments",
|
||||
"link_fieldname": "payment_entry",
|
||||
"parent_doctype": "Bank Transaction",
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-23 12:07:20.887885",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
@@ -786,4 +794,4 @@
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class PaymentEntry(AccountsController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payments",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
)
|
||||
super(PaymentEntry, self).on_cancel()
|
||||
@@ -913,8 +913,11 @@ class PaymentEntry(AccountsController):
|
||||
):
|
||||
return
|
||||
|
||||
total_negative_outstanding = sum(
|
||||
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
||||
total_negative_outstanding = flt(
|
||||
sum(
|
||||
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
||||
),
|
||||
self.references[0].precision("outstanding_amount") if self.references else None,
|
||||
)
|
||||
|
||||
paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
|
||||
@@ -1549,13 +1552,42 @@ def get_outstanding_reference_documents(args):
|
||||
return data
|
||||
|
||||
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||
invoice_ref_based_on_payment_terms = {}
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list:
|
||||
"""Split a list of invoices based on their payment terms."""
|
||||
exc_rates = get_currency_data(outstanding_invoices, company)
|
||||
|
||||
outstanding_invoices_after_split = []
|
||||
for entry in outstanding_invoices:
|
||||
if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if payment_term_template := frappe.db.get_value(
|
||||
entry.voucher_type, entry.voucher_no, "payment_terms_template"
|
||||
):
|
||||
split_rows = get_split_invoice_rows(entry, payment_term_template, exc_rates)
|
||||
if not split_rows:
|
||||
continue
|
||||
|
||||
frappe.msgprint(
|
||||
_("Splitting {0} {1} into {2} rows as per Payment Terms").format(
|
||||
_(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows)
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
outstanding_invoices_after_split += split_rows
|
||||
continue
|
||||
|
||||
# If not an invoice or no payment terms template, add as it is
|
||||
outstanding_invoices_after_split.append(entry)
|
||||
|
||||
return outstanding_invoices_after_split
|
||||
|
||||
|
||||
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict:
|
||||
"""Get currency and conversion data for a list of invoices."""
|
||||
exc_rates = frappe._dict()
|
||||
company_currency = (
|
||||
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||
)
|
||||
exc_rates = frappe._dict()
|
||||
|
||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||
for x in frappe.db.get_all(
|
||||
@@ -1570,71 +1602,54 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||
company_currency=company_currency,
|
||||
)
|
||||
|
||||
for idx, d in enumerate(outstanding_invoices):
|
||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||
payment_term_template = frappe.db.get_value(
|
||||
d.voucher_type, d.voucher_no, "payment_terms_template"
|
||||
return exc_rates
|
||||
|
||||
|
||||
def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: dict) -> list:
|
||||
"""Split invoice based on its payment schedule table."""
|
||||
split_rows = []
|
||||
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
||||
)
|
||||
|
||||
if not allocate_payment_based_on_payment_terms:
|
||||
return [invoice]
|
||||
|
||||
payment_schedule = frappe.get_all(
|
||||
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
|
||||
)
|
||||
for payment_term in payment_schedule:
|
||||
if not payment_term.outstanding > 0.1:
|
||||
continue
|
||||
|
||||
doc_details = exc_rates.get(payment_term.parent, None)
|
||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||
doc_details.party_account_currency != doc_details.company_currency
|
||||
)
|
||||
payment_term_outstanding = flt(payment_term.outstanding)
|
||||
if not is_multi_currency_acc:
|
||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||
|
||||
split_rows.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"due_date": invoice.due_date,
|
||||
"currency": invoice.currency,
|
||||
"voucher_no": invoice.voucher_no,
|
||||
"voucher_type": invoice.voucher_type,
|
||||
"posting_date": invoice.posting_date,
|
||||
"invoice_amount": flt(invoice.invoice_amount),
|
||||
"outstanding_amount": payment_term_outstanding
|
||||
if payment_term_outstanding
|
||||
else invoice.outstanding_amount,
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"payment_amount": payment_term.payment_amount,
|
||||
"payment_term": payment_term.payment_term,
|
||||
}
|
||||
)
|
||||
if payment_term_template:
|
||||
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
||||
)
|
||||
if allocate_payment_based_on_payment_terms:
|
||||
payment_schedule = frappe.get_all(
|
||||
"Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"]
|
||||
)
|
||||
)
|
||||
|
||||
for payment_term in payment_schedule:
|
||||
if payment_term.outstanding > 0.1:
|
||||
doc_details = exc_rates.get(payment_term.parent, None)
|
||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||
doc_details.party_account_currency != doc_details.company_currency
|
||||
)
|
||||
payment_term_outstanding = flt(payment_term.outstanding)
|
||||
if not is_multi_currency_acc:
|
||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||
|
||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||
invoice_ref_based_on_payment_terms[idx].append(
|
||||
frappe._dict(
|
||||
{
|
||||
"due_date": d.due_date,
|
||||
"currency": d.currency,
|
||||
"voucher_no": d.voucher_no,
|
||||
"voucher_type": d.voucher_type,
|
||||
"posting_date": d.posting_date,
|
||||
"invoice_amount": flt(d.invoice_amount),
|
||||
"outstanding_amount": payment_term_outstanding
|
||||
if payment_term_outstanding
|
||||
else d.outstanding_amount,
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"payment_amount": payment_term.payment_amount,
|
||||
"payment_term": payment_term.payment_term,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
outstanding_invoices_after_split = []
|
||||
if invoice_ref_based_on_payment_terms:
|
||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
||||
voucher_no = ref[0]["voucher_no"]
|
||||
voucher_type = ref[0]["voucher_type"]
|
||||
|
||||
frappe.msgprint(
|
||||
_("Spliting {} {} into {} row(s) as per Payment Terms").format(
|
||||
voucher_type, voucher_no, len(ref)
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
|
||||
outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
|
||||
|
||||
existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices))
|
||||
index = outstanding_invoices.index(existing_row[0])
|
||||
outstanding_invoices.pop(index)
|
||||
|
||||
outstanding_invoices_after_split += outstanding_invoices
|
||||
return outstanding_invoices_after_split
|
||||
return split_rows
|
||||
|
||||
|
||||
def get_orders_to_be_billed(
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe.utils import add_days, flt, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
InvalidPaymentEntry,
|
||||
get_outstanding_reference_documents,
|
||||
get_payment_entry,
|
||||
get_reference_details,
|
||||
)
|
||||
@@ -21,6 +23,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
||||
create_sales_invoice,
|
||||
create_sales_invoice_against_cost_center,
|
||||
)
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||
|
||||
@@ -1219,6 +1222,115 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, so.rounded_total)
|
||||
|
||||
def test_outstanding_invoices_api(self):
|
||||
"""
|
||||
Test if `get_outstanding_reference_documents` fetches invoices in the right order.
|
||||
"""
|
||||
customer = create_customer("Max Mustermann", "INR")
|
||||
create_payment_terms_template()
|
||||
|
||||
# SI has an earlier due date and SI2 has a later due date
|
||||
si = create_sales_invoice(
|
||||
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
|
||||
)
|
||||
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
|
||||
si2.payment_terms_template = "Test Receivable Template"
|
||||
si2.submit()
|
||||
|
||||
args = {
|
||||
"posting_date": nowdate(),
|
||||
"company": "_Test Company",
|
||||
"party_type": "Customer",
|
||||
"payment_type": "Pay",
|
||||
"party": customer,
|
||||
"party_account": "Debtors - _TC",
|
||||
}
|
||||
args.update(
|
||||
{
|
||||
"get_outstanding_invoices": True,
|
||||
"from_posting_date": add_days(nowdate(), -4),
|
||||
"to_posting_date": add_days(nowdate(), 2),
|
||||
}
|
||||
)
|
||||
references = get_outstanding_reference_documents(args)
|
||||
|
||||
self.assertEqual(len(references), 3)
|
||||
self.assertEqual(references[0].voucher_no, si.name)
|
||||
self.assertEqual(references[1].voucher_no, si2.name)
|
||||
self.assertEqual(references[2].voucher_no, si2.name)
|
||||
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||
|
||||
def test_partial_cancel_for_payment_entry(self):
|
||||
si = create_sales_invoice()
|
||||
|
||||
pe = get_payment_entry(si.doctype, si.name)
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
# Additional GL Entry
|
||||
tax_amount = 10
|
||||
reference_row = pe.references[0]
|
||||
gl_args = {
|
||||
"party_type": pe.party_type,
|
||||
"party": pe.party,
|
||||
"against_voucher_type": reference_row.reference_doctype,
|
||||
"against_voucher": reference_row.reference_name,
|
||||
"voucher_detail_no": reference_row.name,
|
||||
}
|
||||
|
||||
gl_dicts = []
|
||||
|
||||
gl_dicts.extend(
|
||||
[
|
||||
pe.get_gl_dict(
|
||||
{
|
||||
"account": pe.paid_to,
|
||||
"credit": tax_amount,
|
||||
"credit_in_account_currency": tax_amount,
|
||||
**gl_args,
|
||||
}
|
||||
),
|
||||
pe.get_gl_dict(
|
||||
{
|
||||
"account": pe.paid_from,
|
||||
"debit": tax_amount,
|
||||
"debit_in_account_currency": tax_amount,
|
||||
**gl_args,
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
make_gl_entries(gl_dicts)
|
||||
|
||||
# Assert PLEs Before
|
||||
self.assertPLEntries(
|
||||
pe,
|
||||
[
|
||||
{"amount": -100.0, "against_voucher_no": si.name},
|
||||
{"amount": 10.0, "against_voucher_no": si.name},
|
||||
],
|
||||
)
|
||||
|
||||
# Partially cancel Payment Entry
|
||||
make_reverse_gl_entries(gl_dicts, partial_cancel=True)
|
||||
self.assertPLEntries(pe, [{"amount": -100.0, "against_voucher_no": si.name}])
|
||||
|
||||
def assertPLEntries(self, payment_doc, expected_pl_entries):
|
||||
pl_entries = frappe.get_all(
|
||||
"Payment Ledger Entry",
|
||||
filters={
|
||||
"voucher_type": payment_doc.doctype,
|
||||
"voucher_no": payment_doc.name,
|
||||
"delinked": 0,
|
||||
},
|
||||
fields=["amount", "against_voucher_no"],
|
||||
)
|
||||
out_str = json.dumps(sorted(pl_entries, key=json.dumps))
|
||||
expected_out_str = json.dumps(sorted(expected_pl_entries, key=json.dumps))
|
||||
self.assertEqual(out_str, expected_out_str)
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
@@ -1279,6 +1391,9 @@ def create_payment_terms_template():
|
||||
def create_payment_terms_template_with_discount(
|
||||
name=None, discount_type=None, discount=None, template_name=None
|
||||
):
|
||||
"""
|
||||
Create a Payment Terms Template with % or amount discount.
|
||||
"""
|
||||
create_payment_term(name or "30 Credit Days with 10% Discount")
|
||||
template_name = template_name or "Test Discount Template"
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"party_type",
|
||||
"party",
|
||||
"due_date",
|
||||
"voucher_detail_no",
|
||||
"cost_center",
|
||||
"finance_book",
|
||||
"voucher_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",
|
||||
@@ -143,12 +148,18 @@
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"label": "Remarks"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Detail No",
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-30 16:15:00.470283",
|
||||
"modified": "2023-11-08 10:53:10.664896",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Ledger Entry",
|
||||
|
||||
@@ -93,6 +93,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(
|
||||
"""
|
||||
@@ -116,11 +118,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,
|
||||
}
|
||||
),
|
||||
{
|
||||
@@ -146,7 +150,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"),
|
||||
@@ -154,8 +158,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):
|
||||
|
||||
|
||||
@@ -1137,6 +1137,40 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(pay.unallocated_amount, 1000)
|
||||
self.assertEqual(pay.difference_amount, 0)
|
||||
|
||||
def test_rounding_of_unallocated_amount(self):
|
||||
self.supplier = "_Test Supplier USD"
|
||||
pi = self.create_purchase_invoice(qty=1, rate=10, do_not_submit=True)
|
||||
pi.supplier = self.supplier
|
||||
pi.currency = "USD"
|
||||
pi.conversion_rate = 80
|
||||
pi.credit_to = self.creditors_usd
|
||||
pi.save().submit()
|
||||
|
||||
pe = get_payment_entry(pi.doctype, pi.name)
|
||||
pe.target_exchange_rate = 78.726500000
|
||||
pe.received_amount = 26.75
|
||||
pe.paid_amount = 2105.93
|
||||
pe.references = []
|
||||
pe.save().submit()
|
||||
|
||||
# unallocated_amount will have some rounding loss - 26.749950
|
||||
self.assertNotEqual(pe.unallocated_amount, 26.75)
|
||||
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
pr.party_type = "Supplier"
|
||||
pr.party = self.supplier
|
||||
pr.receivable_payable_account = self.creditors_usd
|
||||
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||
payments = [payment.as_dict() for payment in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
|
||||
# Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
|
||||
pr.reconcile()
|
||||
|
||||
|
||||
def make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"is_pos",
|
||||
"is_return",
|
||||
"update_billed_amount_in_sales_order",
|
||||
"update_billed_amount_in_delivery_note",
|
||||
"column_break1",
|
||||
"company",
|
||||
"posting_date",
|
||||
@@ -1549,12 +1550,19 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval: doc.is_return && doc.return_against",
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-30 03:49:50.455199",
|
||||
"modified": "2023-11-20 12:27:12.848149",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -659,7 +659,7 @@ def get_stock_availability(item_code, warehouse):
|
||||
return bin_qty - pos_sales_qty, is_stock_item
|
||||
else:
|
||||
is_stock_item = True
|
||||
if frappe.db.exists("Product Bundle", item_code):
|
||||
if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}):
|
||||
return get_bundle_availability(item_code, warehouse), is_stock_item
|
||||
else:
|
||||
is_stock_item = False
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -821,7 +822,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-02 12:52:39.125295",
|
||||
"modified": "2023-11-14 18:33:22.585715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
@@ -831,4 +832,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
}
|
||||
|
||||
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
unblock_invoice() {
|
||||
|
||||
@@ -383,7 +383,8 @@
|
||||
"label": "Supplier Invoice No",
|
||||
"oldfieldname": "bill_no",
|
||||
"oldfieldtype": "Data",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
@@ -406,7 +407,8 @@
|
||||
"no_copy": 1,
|
||||
"options": "Purchase Invoice",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_addresses",
|
||||
@@ -1592,7 +1594,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",
|
||||
|
||||
@@ -13,6 +13,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
check_if_return_invoice_linked_with_payment_entry,
|
||||
@@ -494,6 +495,7 @@ class PurchaseInvoice(BuyingController):
|
||||
def validate_for_repost(self):
|
||||
self.validate_write_off_account()
|
||||
self.validate_expense_account()
|
||||
validate_docs_for_voucher_types(["Purchase Invoice"])
|
||||
validate_docs_for_deferred_accounting([], [self.name])
|
||||
|
||||
def on_submit(self):
|
||||
@@ -532,7 +534,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
self.update_project()
|
||||
if (
|
||||
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
|
||||
):
|
||||
self.update_project()
|
||||
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
self.update_advance_tax_references()
|
||||
|
||||
@@ -1269,7 +1275,10 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
self.update_project()
|
||||
if (
|
||||
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
|
||||
):
|
||||
self.update_project()
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
@@ -1287,13 +1296,21 @@ class PurchaseInvoice(BuyingController):
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
def update_project(self):
|
||||
project_list = []
|
||||
projects = frappe._dict()
|
||||
for d in self.items:
|
||||
if d.project and d.project not in project_list:
|
||||
project = frappe.get_doc("Project", d.project)
|
||||
project.update_purchase_costing()
|
||||
project.db_update()
|
||||
project_list.append(d.project)
|
||||
if d.project:
|
||||
if self.docstatus == 1:
|
||||
projects[d.project] = projects.get(d.project, 0) + d.base_net_amount
|
||||
elif self.docstatus == 2:
|
||||
projects[d.project] = projects.get(d.project, 0) - d.base_net_amount
|
||||
|
||||
pj = frappe.qb.DocType("Project")
|
||||
for proj, value in projects.items():
|
||||
res = (
|
||||
frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
|
||||
)
|
||||
current_purchase_cost = res and res[0][0] or 0
|
||||
frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
|
||||
|
||||
def validate_supplier_invoice(self):
|
||||
if self.bill_date:
|
||||
|
||||
@@ -1718,9 +1718,14 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
self.assertTrue(return_pi.docstatus == 1)
|
||||
|
||||
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(
|
||||
@@ -1730,7 +1735,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 (
|
||||
@@ -1833,6 +1838,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,
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -491,6 +492,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
@@ -498,6 +500,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": ":Company",
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"fieldname": "cost_center",
|
||||
@@ -890,7 +893,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,7 @@ 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 = get_allowed_types_from_settings()
|
||||
|
||||
def validate(self):
|
||||
self.validate_vouchers()
|
||||
@@ -53,15 +51,7 @@ class RepostAccountingLedger(Document):
|
||||
|
||||
def validate_vouchers(self):
|
||||
if self.vouchers:
|
||||
# Validate voucher types
|
||||
voucher_types = set([x.voucher_type for x in self.vouchers])
|
||||
if disallowed_types := voucher_types.difference(self._allowed_types):
|
||||
frappe.throw(
|
||||
_("{0} types are not allowed. Only {1} are.").format(
|
||||
frappe.bold(comma_and(list(disallowed_types))),
|
||||
frappe.bold(comma_and(list(self._allowed_types))),
|
||||
)
|
||||
)
|
||||
validate_docs_for_voucher_types([x.voucher_type for x in self.vouchers])
|
||||
|
||||
def get_existing_ledger_entries(self):
|
||||
vouchers = [x.voucher_no for x in self.vouchers]
|
||||
@@ -157,7 +147,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()
|
||||
@@ -165,6 +155,15 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def get_allowed_types_from_settings():
|
||||
return [
|
||||
x.document_type
|
||||
for x in frappe.db.get_all(
|
||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||
docs_with_deferred_revenue = frappe.db.get_all(
|
||||
"Sales Invoice Item",
|
||||
@@ -186,3 +185,37 @@ 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]))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def validate_docs_for_voucher_types(doc_voucher_types):
|
||||
allowed_types = get_allowed_types_from_settings()
|
||||
# Validate voucher types
|
||||
voucher_types = set(doc_voucher_types)
|
||||
if disallowed_types := voucher_types.difference(allowed_types):
|
||||
message = "are" if len(disallowed_types) > 1 else "is"
|
||||
frappe.throw(
|
||||
_("{0} {1} not allowed to be reposted. Modify {2} to enable reposting.").format(
|
||||
frappe.bold(comma_and(list(disallowed_types))),
|
||||
message,
|
||||
frappe.bold(
|
||||
frappe.utils.get_link_to_form(
|
||||
"Repost Accounting Ledger Settings", "Repost Accounting Ledger Settings"
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@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
|
||||
@@ -34,7 +34,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
super.onload();
|
||||
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"];
|
||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||
|
||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
// show debit_to in print format
|
||||
@@ -178,10 +178,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
|
||||
make_maintenance_schedule() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
|
||||
@@ -557,15 +556,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) {
|
||||
@@ -660,6 +650,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",
|
||||
@@ -1612,7 +1613,8 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Inter Company Invoice Reference",
|
||||
"options": "Purchase Invoice",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_group",
|
||||
@@ -2144,6 +2146,13 @@
|
||||
"fieldname": "use_company_roundoff_cost_center",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Company default Cost Center for Round off"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"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",
|
||||
@@ -2156,7 +2165,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-06-19 16:02:05.309332",
|
||||
"modified": "2023-11-23 16:56:29.679499",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -17,6 +17,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
)
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
@@ -180,6 +181,7 @@ class SalesInvoice(SellingController):
|
||||
self.validate_write_off_account()
|
||||
self.validate_account_for_change_amount()
|
||||
self.validate_income_account()
|
||||
validate_docs_for_voucher_types(["Sales Invoice"])
|
||||
validate_docs_for_deferred_accounting([self.name], [])
|
||||
|
||||
def validate_fixed_asset(self):
|
||||
@@ -261,6 +263,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()
|
||||
|
||||
@@ -407,7 +410,7 @@ class SalesInvoice(SellingController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payments",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
)
|
||||
@@ -1036,7 +1039,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
|
||||
)
|
||||
@@ -1271,7 +1274,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(
|
||||
@@ -1433,6 +1436,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:
|
||||
|
||||
@@ -782,6 +782,28 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
w = self.make()
|
||||
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
|
||||
|
||||
def test_rounded_total_with_cash_discount(self):
|
||||
si = frappe.copy_doc(test_records[2])
|
||||
|
||||
item = copy.deepcopy(si.get("items")[0])
|
||||
item.update(
|
||||
{
|
||||
"qty": 1,
|
||||
"rate": 14960.66,
|
||||
}
|
||||
)
|
||||
|
||||
si.set("items", [item])
|
||||
si.set("taxes", [])
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.is_cash_or_non_trade_discount = 1
|
||||
si.discount_amount = 1
|
||||
si.insert()
|
||||
|
||||
self.assertEqual(si.grand_total, 14959.66)
|
||||
self.assertEqual(si.rounded_total, 14960)
|
||||
self.assertEqual(si.rounding_adjustment, 0.34)
|
||||
|
||||
def test_payment(self):
|
||||
w = self.make()
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -891,7 +892,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-25 11:58:10.723833",
|
||||
"modified": "2023-11-14 18:34:10.479329",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
@@ -901,4 +902,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
|
||||
|
||||
class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
@@ -73,7 +73,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
@@ -138,7 +138,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe2.doctype,
|
||||
"voucher_no": pe2.name,
|
||||
@@ -196,7 +196,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
@@ -281,7 +281,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe2.doctype,
|
||||
"voucher_no": pe2.name,
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Unreconcile Payments", {
|
||||
frappe.ui.form.on("Unreconcile Payment", {
|
||||
refresh(frm) {
|
||||
frm.set_query("voucher_type", function() {
|
||||
return {
|
||||
@@ -21,7 +21,7 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Unreconcile Payments",
|
||||
"options": "Unreconcile Payment",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -61,7 +61,7 @@
|
||||
"modified": "2023-08-28 17:42:50.261377",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Unreconcile Payments",
|
||||
"name": "Unreconcile Payment",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
@@ -90,4 +90,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ from erpnext.accounts.utils import (
|
||||
)
|
||||
|
||||
|
||||
class UnreconcilePayments(Document):
|
||||
class UnreconcilePayment(Document):
|
||||
def validate(self):
|
||||
self.supported_types = ["Payment Entry", "Journal Entry"]
|
||||
if not self.voucher_type in self.supported_types:
|
||||
@@ -142,7 +142,7 @@ def create_unreconcile_doc_for_selection(selections=None):
|
||||
selections = frappe.json.loads(selections)
|
||||
# assuming each row is a unique voucher
|
||||
for row in selections:
|
||||
unrecon = frappe.new_doc("Unreconcile Payments")
|
||||
unrecon = frappe.new_doc("Unreconcile Payment")
|
||||
unrecon.company = row.get("company")
|
||||
unrecon.voucher_type = row.get("voucher_type")
|
||||
unrecon.voucher_no = row.get("voucher_no")
|
||||
@@ -556,7 +556,12 @@ def get_round_off_account_and_cost_center(
|
||||
|
||||
|
||||
def make_reverse_gl_entries(
|
||||
gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes"
|
||||
gl_entries=None,
|
||||
voucher_type=None,
|
||||
voucher_no=None,
|
||||
adv_adj=False,
|
||||
update_outstanding="Yes",
|
||||
partial_cancel=False,
|
||||
):
|
||||
"""
|
||||
Get original gl entries of the voucher
|
||||
@@ -576,14 +581,19 @@ def make_reverse_gl_entries(
|
||||
|
||||
if gl_entries:
|
||||
create_payment_ledger_entry(
|
||||
gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
|
||||
gl_entries,
|
||||
cancel=1,
|
||||
adv_adj=adv_adj,
|
||||
update_outstanding=update_outstanding,
|
||||
partial_cancel=partial_cancel,
|
||||
)
|
||||
validate_accounting_period(gl_entries)
|
||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||
|
||||
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
|
||||
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
|
||||
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
||||
if not partial_cancel:
|
||||
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
||||
|
||||
for entry in gl_entries:
|
||||
new_gle = copy.deepcopy(entry)
|
||||
|
||||
@@ -31,7 +31,12 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
||||
from erpnext.utilities.regional import temporary_flag
|
||||
|
||||
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
|
||||
PURCHASE_TRANSACTION_TYPES = {
|
||||
"Supplier Quotation",
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
}
|
||||
SALES_TRANSACTION_TYPES = {
|
||||
"Quotation",
|
||||
"Sales Order",
|
||||
@@ -231,7 +236,9 @@ def set_address_details(
|
||||
if shipping_address:
|
||||
party_details.update(
|
||||
shipping_address=shipping_address,
|
||||
shipping_address_display=render_address(shipping_address),
|
||||
shipping_address_display=render_address(
|
||||
shipping_address, check_permissions=not ignore_permissions
|
||||
),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||
)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ from collections import OrderedDict
|
||||
import frappe
|
||||
from frappe import _, qb, scrub
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Date, Sum
|
||||
from frappe.query_builder.functions import Date, Substring, Sum
|
||||
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
@@ -116,7 +116,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 +188,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 +200,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 +281,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) > 0.0 / 10**self.currency_precision) or (
|
||||
(abs(row.outstanding_in_account_currency) > 0.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 +741,7 @@ class ReceivablePayableReport(object):
|
||||
query = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.name,
|
||||
ple.account,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
@@ -731,13 +755,20 @@ 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"):
|
||||
if remarks_length := frappe.db.get_single_value(
|
||||
"Accounts Settings", "receivable_payable_remarks_length"
|
||||
):
|
||||
query = query.select(Substring(ple.remarks, 1, remarks_length).as_("remarks"))
|
||||
else:
|
||||
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 +854,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 +1152,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) {
|
||||
|
||||
@@ -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},
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
},
|
||||
{
|
||||
"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
|
||||
},
|
||||
@@ -188,7 +188,13 @@ frappe.query_reports["General Ledger"] = {
|
||||
"fieldname": "show_net_values_in_party_account",
|
||||
"label": __("Show Net Values in Party Account"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,14 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
select_fields = """, debit, credit, debit_in_account_currency,
|
||||
credit_in_account_currency """
|
||||
|
||||
if filters.get("show_remarks"):
|
||||
if remarks_length := frappe.db.get_single_value(
|
||||
"Accounts Settings", "general_ledger_remarks_length"
|
||||
):
|
||||
select_fields += f",substr(remarks, 1, {remarks_length}) as 'remarks'"
|
||||
else:
|
||||
select_fields += """,remarks"""
|
||||
|
||||
order_by_statement = "order by posting_date, account, creation"
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
@@ -189,7 +197,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
voucher_type, voucher_no, {dimension_fields}
|
||||
cost_center, project,
|
||||
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}
|
||||
@@ -249,9 +257,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:
|
||||
@@ -593,8 +599,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
|
||||
|
||||
@@ -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" and tax_amount and rate:
|
||||
# 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":
|
||||
@@ -176,54 +184,73 @@ def get_columns(filters):
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
else:
|
||||
columns.append(
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||
]
|
||||
)
|
||||
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": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 60,
|
||||
},
|
||||
{
|
||||
"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,
|
||||
},
|
||||
{
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_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 +258,12 @@ def get_columns(filters):
|
||||
"options": "transaction_type",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
@@ -253,27 +286,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 +322,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 +389,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 +409,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":
|
||||
|
||||
@@ -96,7 +96,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
},
|
||||
{
|
||||
"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), ""]))
|
||||
|
||||
@@ -179,6 +179,7 @@ def get_balance_on(
|
||||
in_account_currency=True,
|
||||
cost_center=None,
|
||||
ignore_account_permission=False,
|
||||
start_date=None,
|
||||
):
|
||||
if not account and frappe.form_dict.get("account"):
|
||||
account = frappe.form_dict.get("account")
|
||||
@@ -192,6 +193,8 @@ def get_balance_on(
|
||||
cost_center = frappe.form_dict.get("cost_center")
|
||||
|
||||
cond = ["is_cancelled=0"]
|
||||
if start_date:
|
||||
cond.append("posting_date >= %s" % frappe.db.escape(cstr(start_date)))
|
||||
if date:
|
||||
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
|
||||
else:
|
||||
@@ -511,7 +514,7 @@ def check_if_advance_entry_modified(args):
|
||||
party_account_field = (
|
||||
"paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to"
|
||||
)
|
||||
|
||||
precision = frappe.get_precision("Payment Entry", "unallocated_amount")
|
||||
if args.voucher_detail_no:
|
||||
ret = frappe.db.sql(
|
||||
"""select t1.name
|
||||
@@ -533,9 +536,9 @@ def check_if_advance_entry_modified(args):
|
||||
where
|
||||
name = %(voucher_no)s and docstatus = 1
|
||||
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
|
||||
and unallocated_amount = %(unreconciled_amount)s
|
||||
and round(unallocated_amount, {1}) = round(%(unreconciled_amount)s, {1})
|
||||
""".format(
|
||||
party_account_field
|
||||
party_account_field, precision
|
||||
),
|
||||
args,
|
||||
)
|
||||
@@ -1617,6 +1620,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
due_date=gle.due_date,
|
||||
voucher_type=gle.voucher_type,
|
||||
voucher_no=gle.voucher_no,
|
||||
voucher_detail_no=gle.voucher_detail_no,
|
||||
against_voucher_type=gle.against_voucher_type
|
||||
if gle.against_voucher_type
|
||||
else gle.voucher_type,
|
||||
@@ -1638,7 +1642,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
|
||||
|
||||
def create_payment_ledger_entry(
|
||||
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0
|
||||
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
|
||||
):
|
||||
if gl_entries:
|
||||
ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel)
|
||||
@@ -1648,7 +1652,7 @@ def create_payment_ledger_entry(
|
||||
ple = frappe.get_doc(entry)
|
||||
|
||||
if cancel:
|
||||
delink_original_entry(ple)
|
||||
delink_original_entry(ple, partial_cancel=partial_cancel)
|
||||
|
||||
ple.flags.ignore_permissions = 1
|
||||
ple.flags.adv_adj = adv_adj
|
||||
@@ -1695,7 +1699,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
||||
ref_doc.set_status(update=True)
|
||||
|
||||
|
||||
def delink_original_entry(pl_entry):
|
||||
def delink_original_entry(pl_entry, partial_cancel=False):
|
||||
if pl_entry:
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
query = (
|
||||
@@ -1715,6 +1719,10 @@ def delink_original_entry(pl_entry):
|
||||
& (ple.against_voucher_no == pl_entry.against_voucher_no)
|
||||
)
|
||||
)
|
||||
|
||||
if partial_cancel:
|
||||
query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no)
|
||||
|
||||
query.run()
|
||||
|
||||
|
||||
@@ -1791,6 +1799,28 @@ class QueryPaymentLedger(object):
|
||||
Table("outstanding").amount_in_account_currency >= self.max_outstanding
|
||||
)
|
||||
|
||||
if self.limit and self.get_invoices:
|
||||
outstanding_vouchers = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.against_voucher_no.as_("voucher_no"),
|
||||
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
|
||||
)
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(filter_on_against_voucher_no))
|
||||
.where(Criterion.all(self.common_filter))
|
||||
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
|
||||
.orderby(ple.posting_date, ple.voucher_no)
|
||||
.having(qb.Field("amount_in_account_currency") > 0)
|
||||
.limit(self.limit)
|
||||
.run()
|
||||
)
|
||||
if outstanding_vouchers:
|
||||
filter_on_voucher_no.append(ple.voucher_no.isin([x[0] for x in outstanding_vouchers]))
|
||||
filter_on_against_voucher_no.append(
|
||||
ple.against_voucher_no.isin([x[0] for x in outstanding_vouchers])
|
||||
)
|
||||
|
||||
# build query for voucher amount
|
||||
query_voucher_amount = (
|
||||
qb.from_(ple)
|
||||
|
||||
@@ -495,11 +495,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval.doc.asset_quantity",
|
||||
"default": "1",
|
||||
"fieldname": "asset_quantity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Asset Quantity",
|
||||
"read_only": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "depr_entry_posting_status",
|
||||
@@ -565,7 +565,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-27 17:03:46.629617",
|
||||
"modified": "2023-11-20 21:05:45.216899",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -1216,11 +1216,12 @@ 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),
|
||||
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||
"rate_of_depreciation": d.rate_of_depreciation,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1396,7 +1397,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(
|
||||
@@ -1441,7 +1442,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)
|
||||
|
||||
@@ -464,6 +464,9 @@ def restore_asset(asset_name):
|
||||
|
||||
|
||||
def depreciate_asset(asset, date):
|
||||
if not asset.calculate_depreciation:
|
||||
return
|
||||
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.prepare_depreciation_data(date_of_disposal=date)
|
||||
asset.save()
|
||||
@@ -472,6 +475,9 @@ def depreciate_asset(asset, date):
|
||||
|
||||
|
||||
def reset_depreciation_schedule(asset, date):
|
||||
if not asset.calculate_depreciation:
|
||||
return
|
||||
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
|
||||
# recreate original depreciation schedule of the asset
|
||||
@@ -740,6 +746,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 asset_doc.calculate_depreciation:
|
||||
asset_doc.prepare_depreciation_data(getdate(disposal_date))
|
||||
|
||||
|
||||
@@ -731,7 +731,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",
|
||||
@@ -740,7 +742,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 = [
|
||||
@@ -1703,7 +1705,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,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -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",
|
||||
@@ -82,23 +82,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 22:21:52.090191",
|
||||
"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
|
||||
@@ -17,6 +17,7 @@
|
||||
"po_required",
|
||||
"pr_required",
|
||||
"blanket_order_allowance",
|
||||
"project_update_frequency",
|
||||
"column_break_12",
|
||||
"maintain_same_rate",
|
||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||
@@ -172,6 +173,14 @@
|
||||
"fieldname": "blanket_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Blanket Order Allowance (%)"
|
||||
},
|
||||
{
|
||||
"default": "Each Transaction",
|
||||
"description": "How often should Project be updated of Total Purchase Cost ?",
|
||||
"fieldname": "project_update_frequency",
|
||||
"fieldtype": "Select",
|
||||
"label": "Update frequency of Project",
|
||||
"options": "Each Transaction\nManual"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -179,7 +188,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-25 14:03:32.520418",
|
||||
"modified": "2023-11-24 10:55:51.287327",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -16,7 +16,7 @@ from erpnext.buying.doctype.purchase_order.purchase_order import (
|
||||
make_purchase_invoice as make_pi_from_po,
|
||||
)
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
||||
from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate
|
||||
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||
@@ -27,6 +27,21 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||
|
||||
|
||||
class TestPurchaseOrder(FrappeTestCase):
|
||||
def test_purchase_order_qty(self):
|
||||
po = create_purchase_order(qty=1, do_not_save=True)
|
||||
po.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"qty": -1,
|
||||
"rate": 10,
|
||||
},
|
||||
)
|
||||
self.assertRaises(frappe.NonNegativeError, po.save)
|
||||
|
||||
po.items[1].qty = 0
|
||||
self.assertRaises(InvalidQtyError, po.save)
|
||||
|
||||
def test_make_purchase_receipt(self):
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -214,6 +215,7 @@
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Quantity",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"print_width": "60px",
|
||||
@@ -471,6 +473,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",
|
||||
@@ -486,6 +489,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",
|
||||
@@ -915,7 +919,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-27 15:50:42.655573",
|
||||
"modified": "2023-11-24 19:07:34.921094",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"company",
|
||||
"billing_address",
|
||||
"billing_address_display",
|
||||
"vendor",
|
||||
"column_break1",
|
||||
"transaction_date",
|
||||
@@ -292,13 +294,25 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Document Print",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_address",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company Billing Address",
|
||||
"options": "Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_address_display",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Billing Address Details",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-09 12:20:26.850623",
|
||||
"modified": "2023-11-06 12:45:28.898706",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -261,13 +262,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
|
||||
}
|
||||
@@ -170,7 +170,7 @@
|
||||
"fieldname": "supplier_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Supplier Type",
|
||||
"options": "Company\nIndividual",
|
||||
"options": "Company\nIndividual\nProprietorship\nPartnership",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -457,7 +457,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2023-05-09 15:34:13.408932",
|
||||
"modified": "2023-10-19 16:55:15.148325",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
@@ -525,4 +525,4 @@
|
||||
"states": [],
|
||||
"title_field": "supplier_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,16 +143,17 @@ class Supplier(TransactionBase):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
|
||||
supplier = filters.get("supplier")
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
`tabContact`.name from `tabContact`,
|
||||
`tabDynamic Link`
|
||||
WHERE
|
||||
`tabContact`.name = `tabDynamic Link`.parent
|
||||
and `tabDynamic Link`.link_name = %(supplier)s
|
||||
and `tabDynamic Link`.link_doctype = 'Supplier'
|
||||
and `tabContact`.name like %(txt)s
|
||||
""",
|
||||
{"supplier": supplier, "txt": "%%%s%%" % txt},
|
||||
)
|
||||
contact = frappe.qb.DocType("Contact")
|
||||
dynamic_link = frappe.qb.DocType("Dynamic Link")
|
||||
|
||||
return (
|
||||
frappe.qb.from_(contact)
|
||||
.join(dynamic_link)
|
||||
.on(contact.name == dynamic_link.parent)
|
||||
.select(contact.name, contact.email_id)
|
||||
.where(
|
||||
(dynamic_link.link_name == supplier)
|
||||
& (dynamic_link.link_doctype == "Supplier")
|
||||
& (contact.name.like("%{0}%".format(txt)))
|
||||
)
|
||||
).run(as_dict=False)
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
"valid_till",
|
||||
"quotation_number",
|
||||
"amended_from",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"currency_and_price_list",
|
||||
"currency",
|
||||
"conversion_rate",
|
||||
@@ -79,6 +83,7 @@
|
||||
"pricing_rule_details",
|
||||
"pricing_rules",
|
||||
"address_and_contact_tab",
|
||||
"supplier_address_section",
|
||||
"supplier_address",
|
||||
"address_display",
|
||||
"column_break_72",
|
||||
@@ -86,6 +91,14 @@
|
||||
"contact_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"shipping_address_section",
|
||||
"shipping_address",
|
||||
"column_break_zjaq",
|
||||
"shipping_address_display",
|
||||
"company_billing_address_section",
|
||||
"billing_address",
|
||||
"column_break_gcth",
|
||||
"billing_address_display",
|
||||
"terms_tab",
|
||||
"tc_name",
|
||||
"terms",
|
||||
@@ -837,6 +850,76 @@
|
||||
"fieldname": "named_place",
|
||||
"fieldtype": "Data",
|
||||
"label": "Named Place"
|
||||
},
|
||||
{
|
||||
"fieldname": "shipping_address",
|
||||
"fieldtype": "Link",
|
||||
"label": "Shipping Address",
|
||||
"options": "Address",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_zjaq",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "shipping_address_display",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Shipping Address Details",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shipping_address_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shipping Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_address_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Supplier Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_billing_address_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Company Billing Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_address",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company Billing Address",
|
||||
"options": "Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gcth",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_address_display",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Billing Address Details",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
@@ -844,7 +927,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-14 16:43:41.714832",
|
||||
"modified": "2023-11-20 11:15:30.083077",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
"column_break_15",
|
||||
"manufacturer_part_no",
|
||||
"ad_sec_break",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"section_break_44",
|
||||
"page_break"
|
||||
@@ -134,6 +136,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -554,19 +557,31 @@
|
||||
"fieldname": "expected_delivery_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected Delivery Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-19 12:36:26.913211",
|
||||
"modified": "2023-11-17 12:25:26.235367",
|
||||
"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
|
||||
}
|
||||
@@ -70,6 +70,10 @@ class AccountMissingError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidQtyError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
force_item_fields = (
|
||||
"item_group",
|
||||
"brand",
|
||||
@@ -238,7 +242,7 @@ class AccountsController(TransactionBase):
|
||||
references_map.setdefault(x.parent, []).append(x.name)
|
||||
|
||||
for doc, rows in references_map.items():
|
||||
unreconcile_doc = frappe.get_doc("Unreconcile Payments", doc)
|
||||
unreconcile_doc = frappe.get_doc("Unreconcile Payment", doc)
|
||||
for row in rows:
|
||||
unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0])
|
||||
|
||||
@@ -247,9 +251,9 @@ class AccountsController(TransactionBase):
|
||||
unreconcile_doc.save(ignore_permissions=True)
|
||||
|
||||
# delete docs upon parent doc deletion
|
||||
unreconcile_docs = frappe.db.get_all("Unreconcile Payments", filters={"voucher_no": self.name})
|
||||
unreconcile_docs = frappe.db.get_all("Unreconcile Payment", filters={"voucher_no": self.name})
|
||||
for x in unreconcile_docs:
|
||||
_doc = frappe.get_doc("Unreconcile Payments", x.name)
|
||||
_doc = frappe.get_doc("Unreconcile Payment", x.name)
|
||||
if _doc.docstatus == 1:
|
||||
_doc.cancel()
|
||||
_doc.delete()
|
||||
@@ -895,10 +899,16 @@ class AccountsController(TransactionBase):
|
||||
return gl_dict
|
||||
|
||||
def validate_qty_is_not_zero(self):
|
||||
if self.doctype != "Purchase Receipt":
|
||||
for item in self.items:
|
||||
if not item.qty:
|
||||
frappe.throw(_("Item quantity can not be zero"))
|
||||
if self.doctype == "Purchase Receipt":
|
||||
return
|
||||
|
||||
for item in self.items:
|
||||
if not flt(item.qty):
|
||||
frappe.throw(
|
||||
msg=_("Row #{0}: Item quantity cannot be zero").format(item.idx),
|
||||
title=_("Invalid Quantity"),
|
||||
exc=InvalidQtyError,
|
||||
)
|
||||
|
||||
def validate_account_currency(self, account, account_currency=None):
|
||||
valid_currency = [self.company_currency]
|
||||
@@ -2249,6 +2259,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)
|
||||
@@ -3022,16 +3033,19 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
conv_fac_precision = child_item.precision("conversion_factor") or 2
|
||||
qty_precision = child_item.precision("qty") or 2
|
||||
|
||||
if flt(child_item.billed_amt, rate_precision) > flt(
|
||||
flt(d.get("rate"), rate_precision) * flt(d.get("qty"), qty_precision), rate_precision
|
||||
):
|
||||
# Amount cannot be lesser than billed amount, except for negative amounts
|
||||
row_rate = flt(d.get("rate"), rate_precision)
|
||||
amount_below_billed_amt = flt(child_item.billed_amt, rate_precision) > flt(
|
||||
row_rate * flt(d.get("qty"), qty_precision), rate_precision
|
||||
)
|
||||
if amount_below_billed_amt and row_rate > 0.0:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.").format(
|
||||
child_item.idx, child_item.item_code
|
||||
)
|
||||
)
|
||||
else:
|
||||
child_item.rate = flt(d.get("rate"), rate_precision)
|
||||
child_item.rate = row_rate
|
||||
|
||||
if d.get("conversion_factor"):
|
||||
if child_item.stock_uom == child_item.uom:
|
||||
|
||||
@@ -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, cstr, flt, getdate
|
||||
from frappe.utils.data import nowtime
|
||||
|
||||
@@ -78,26 +78,27 @@ 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_no": row.serial_no,
|
||||
"batch_no": row.batch_no,
|
||||
"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)
|
||||
@@ -219,7 +220,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
|
||||
@@ -336,7 +339,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_no": d.get("serial_no"),
|
||||
@@ -743,7 +746,7 @@ class BuyingController(SubcontractingController):
|
||||
"calculate_depreciation": 1,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"asset_quantity": row.qty if is_grouped_asset else 0,
|
||||
"asset_quantity": row.qty if is_grouped_asset else 1,
|
||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||
"cost_center": row.cost_center,
|
||||
|
||||
@@ -560,6 +560,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"
|
||||
|
||||
@@ -356,6 +356,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
|
||||
doc.consolidated_invoice = ""
|
||||
doc.set("payments", [])
|
||||
doc.update_billed_amount_in_delivery_note = True
|
||||
for data in source.payments:
|
||||
paid_amount = 0.00
|
||||
base_paid_amount = 0.00
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user