Merge branch 'develop' into subcontract-supplied-items
This commit is contained in:
commit
f5ebaed191
1
.github/workflows/patch.yml
vendored
1
.github/workflows/patch.yml
vendored
@ -115,4 +115,5 @@ jobs:
|
|||||||
echo "Updating to latest version"
|
echo "Updating to latest version"
|
||||||
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||||
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
||||||
|
bench setup requirements --python
|
||||||
bench --site test_site migrate
|
bench --site test_site migrate
|
||||||
|
12
.mergify.yml
12
.mergify.yml
@ -9,6 +9,8 @@ pull_request_rules:
|
|||||||
- author!=nabinhait
|
- author!=nabinhait
|
||||||
- author!=ankush
|
- author!=ankush
|
||||||
- author!=deepeshgarg007
|
- author!=deepeshgarg007
|
||||||
|
- author!=mergify[bot]
|
||||||
|
|
||||||
- or:
|
- or:
|
||||||
- base=version-13
|
- base=version-13
|
||||||
- base=version-12
|
- base=version-12
|
||||||
@ -19,6 +21,16 @@ pull_request_rules:
|
|||||||
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
|
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
|
||||||
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
|
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
|
||||||
|
|
||||||
|
- name: Auto-close PRs on pre-release branch
|
||||||
|
conditions:
|
||||||
|
- base=version-13-pre-release
|
||||||
|
actions:
|
||||||
|
close:
|
||||||
|
comment:
|
||||||
|
message: |
|
||||||
|
@{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
|
||||||
|
|
||||||
|
|
||||||
- name: backport to develop
|
- name: backport to develop
|
||||||
conditions:
|
conditions:
|
||||||
- label="backport develop"
|
- label="backport develop"
|
||||||
|
@ -172,11 +172,6 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
"label": __("Show Sales Person"),
|
"label": __("Show Sales Person"),
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "show_remarks",
|
|
||||||
"label": __("Show Remarks"),
|
|
||||||
"fieldtype": "Check",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "tax_id",
|
"fieldname": "tax_id",
|
||||||
"label": __("Tax Id"),
|
"label": __("Tax Id"),
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, scrub
|
from frappe import _, qb, scrub
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
from frappe.query_builder.functions import Date
|
||||||
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
@ -41,6 +43,8 @@ def execute(filters=None):
|
|||||||
class ReceivablePayableReport(object):
|
class ReceivablePayableReport(object):
|
||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
self.filters = frappe._dict(filters or {})
|
self.filters = frappe._dict(filters or {})
|
||||||
|
self.qb_selection_filter = []
|
||||||
|
self.ple = qb.DocType("Payment Ledger Entry")
|
||||||
self.filters.report_date = getdate(self.filters.report_date or nowdate())
|
self.filters.report_date = getdate(self.filters.report_date or nowdate())
|
||||||
self.age_as_on = (
|
self.age_as_on = (
|
||||||
getdate(nowdate())
|
getdate(nowdate())
|
||||||
@ -78,7 +82,7 @@ class ReceivablePayableReport(object):
|
|||||||
self.skip_total_row = 1
|
self.skip_total_row = 1
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
self.get_gl_entries()
|
self.get_ple_entries()
|
||||||
self.get_sales_invoices_or_customers_based_on_sales_person()
|
self.get_sales_invoices_or_customers_based_on_sales_person()
|
||||||
self.voucher_balance = OrderedDict()
|
self.voucher_balance = OrderedDict()
|
||||||
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
|
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
|
||||||
@ -96,25 +100,25 @@ class ReceivablePayableReport(object):
|
|||||||
self.get_return_entries()
|
self.get_return_entries()
|
||||||
|
|
||||||
self.data = []
|
self.data = []
|
||||||
for gle in self.gl_entries:
|
|
||||||
self.update_voucher_balance(gle)
|
for ple in self.ple_entries:
|
||||||
|
self.update_voucher_balance(ple)
|
||||||
|
|
||||||
self.build_data()
|
self.build_data()
|
||||||
|
|
||||||
def init_voucher_balance(self):
|
def init_voucher_balance(self):
|
||||||
# build all keys, since we want to exclude vouchers beyond the report date
|
# build all keys, since we want to exclude vouchers beyond the report date
|
||||||
for gle in self.gl_entries:
|
for ple in self.ple_entries:
|
||||||
# get the balance object for voucher_type
|
# get the balance object for voucher_type
|
||||||
key = (gle.voucher_type, gle.voucher_no, gle.party)
|
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||||
if not key in self.voucher_balance:
|
if not key in self.voucher_balance:
|
||||||
self.voucher_balance[key] = frappe._dict(
|
self.voucher_balance[key] = frappe._dict(
|
||||||
voucher_type=gle.voucher_type,
|
voucher_type=ple.voucher_type,
|
||||||
voucher_no=gle.voucher_no,
|
voucher_no=ple.voucher_no,
|
||||||
party=gle.party,
|
party=ple.party,
|
||||||
party_account=gle.account,
|
party_account=ple.account,
|
||||||
posting_date=gle.posting_date,
|
posting_date=ple.posting_date,
|
||||||
account_currency=gle.account_currency,
|
account_currency=ple.account_currency,
|
||||||
remarks=gle.remarks if self.filters.get("show_remarks") else None,
|
|
||||||
invoiced=0.0,
|
invoiced=0.0,
|
||||||
paid=0.0,
|
paid=0.0,
|
||||||
credit_note=0.0,
|
credit_note=0.0,
|
||||||
@ -124,23 +128,22 @@ class ReceivablePayableReport(object):
|
|||||||
credit_note_in_account_currency=0.0,
|
credit_note_in_account_currency=0.0,
|
||||||
outstanding_in_account_currency=0.0,
|
outstanding_in_account_currency=0.0,
|
||||||
)
|
)
|
||||||
self.get_invoices(gle)
|
|
||||||
|
|
||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
self.init_subtotal_row(gle.party)
|
self.init_subtotal_row(ple.party)
|
||||||
|
|
||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
self.init_subtotal_row("Total")
|
self.init_subtotal_row("Total")
|
||||||
|
|
||||||
def get_invoices(self, gle):
|
def get_invoices(self, ple):
|
||||||
if gle.voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
if ple.voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
||||||
if self.filters.get("sales_person"):
|
if self.filters.get("sales_person"):
|
||||||
if gle.voucher_no in self.sales_person_records.get(
|
if ple.voucher_no in self.sales_person_records.get(
|
||||||
"Sales Invoice", []
|
"Sales Invoice", []
|
||||||
) or gle.party in self.sales_person_records.get("Customer", []):
|
) or ple.party in self.sales_person_records.get("Customer", []):
|
||||||
self.invoices.add(gle.voucher_no)
|
self.invoices.add(ple.voucher_no)
|
||||||
else:
|
else:
|
||||||
self.invoices.add(gle.voucher_no)
|
self.invoices.add(ple.voucher_no)
|
||||||
|
|
||||||
def init_subtotal_row(self, party):
|
def init_subtotal_row(self, party):
|
||||||
if not self.total_row_map.get(party):
|
if not self.total_row_map.get(party):
|
||||||
@ -162,39 +165,49 @@ class ReceivablePayableReport(object):
|
|||||||
"range5",
|
"range5",
|
||||||
]
|
]
|
||||||
|
|
||||||
def update_voucher_balance(self, gle):
|
def get_voucher_balance(self, ple):
|
||||||
|
if self.filters.get("sales_person"):
|
||||||
|
if not (
|
||||||
|
ple.party in self.sales_person_records.get("Customer", [])
|
||||||
|
or ple.against_voucher_no in self.sales_person_records.get("Sales Invoice", [])
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||||
|
row = self.voucher_balance.get(key)
|
||||||
|
return row
|
||||||
|
|
||||||
|
def update_voucher_balance(self, ple):
|
||||||
# get the row where this balance needs to be updated
|
# get the row where this balance needs to be updated
|
||||||
# if its a payment, it will return the linked invoice or will be considered as advance
|
# if its a payment, it will return the linked invoice or will be considered as advance
|
||||||
row = self.get_voucher_balance(gle)
|
row = self.get_voucher_balance(ple)
|
||||||
if not row:
|
if not row:
|
||||||
return
|
return
|
||||||
# gle_balance will be the total "debit - credit" for receivable type reports and
|
|
||||||
# and vice-versa for payable type reports
|
|
||||||
gle_balance = self.get_gle_balance(gle)
|
|
||||||
gle_balance_in_account_currency = self.get_gle_balance_in_account_currency(gle)
|
|
||||||
|
|
||||||
if gle_balance > 0:
|
amount = ple.amount
|
||||||
if gle.voucher_type in ("Journal Entry", "Payment Entry") and gle.against_voucher:
|
amount_in_account_currency = ple.amount_in_account_currency
|
||||||
# debit against sales / purchase invoice
|
|
||||||
row.paid -= gle_balance
|
# update voucher
|
||||||
row.paid_in_account_currency -= gle_balance_in_account_currency
|
if ple.amount > 0:
|
||||||
|
if (
|
||||||
|
ple.voucher_type in ["Journal Entry", "Payment Entry"]
|
||||||
|
and ple.voucher_no != ple.against_voucher_no
|
||||||
|
):
|
||||||
|
row.paid -= amount
|
||||||
|
row.paid_in_account_currency -= amount_in_account_currency
|
||||||
else:
|
else:
|
||||||
# invoice
|
row.invoiced += amount
|
||||||
row.invoiced += gle_balance
|
row.invoiced_in_account_currency += amount_in_account_currency
|
||||||
row.invoiced_in_account_currency += gle_balance_in_account_currency
|
|
||||||
else:
|
else:
|
||||||
# payment or credit note for receivables
|
if self.is_invoice(ple):
|
||||||
if self.is_invoice(gle):
|
row.credit_note -= amount
|
||||||
# stand alone debit / credit note
|
row.credit_note_in_account_currency -= amount_in_account_currency
|
||||||
row.credit_note -= gle_balance
|
|
||||||
row.credit_note_in_account_currency -= gle_balance_in_account_currency
|
|
||||||
else:
|
else:
|
||||||
# advance / unlinked payment or other adjustment
|
row.paid -= amount
|
||||||
row.paid -= gle_balance
|
row.paid_in_account_currency -= amount_in_account_currency
|
||||||
row.paid_in_account_currency -= gle_balance_in_account_currency
|
|
||||||
|
|
||||||
if gle.cost_center:
|
if ple.cost_center:
|
||||||
row.cost_center = str(gle.cost_center)
|
row.cost_center = str(ple.cost_center)
|
||||||
|
|
||||||
def update_sub_total_row(self, row, party):
|
def update_sub_total_row(self, row, party):
|
||||||
total_row = self.total_row_map.get(party)
|
total_row = self.total_row_map.get(party)
|
||||||
@ -210,39 +223,6 @@ class ReceivablePayableReport(object):
|
|||||||
self.data.append({})
|
self.data.append({})
|
||||||
self.update_sub_total_row(sub_total_row, "Total")
|
self.update_sub_total_row(sub_total_row, "Total")
|
||||||
|
|
||||||
def get_voucher_balance(self, gle):
|
|
||||||
if self.filters.get("sales_person"):
|
|
||||||
against_voucher = gle.against_voucher or gle.voucher_no
|
|
||||||
if not (
|
|
||||||
gle.party in self.sales_person_records.get("Customer", [])
|
|
||||||
or against_voucher in self.sales_person_records.get("Sales Invoice", [])
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
voucher_balance = None
|
|
||||||
if gle.against_voucher:
|
|
||||||
# find invoice
|
|
||||||
against_voucher = gle.against_voucher
|
|
||||||
|
|
||||||
# If payment is made against credit note
|
|
||||||
# and credit note is made against a Sales Invoice
|
|
||||||
# then consider the payment against original sales invoice.
|
|
||||||
if gle.against_voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
|
||||||
if gle.against_voucher in self.return_entries:
|
|
||||||
return_against = self.return_entries.get(gle.against_voucher)
|
|
||||||
if return_against:
|
|
||||||
against_voucher = return_against
|
|
||||||
|
|
||||||
voucher_balance = self.voucher_balance.get(
|
|
||||||
(gle.against_voucher_type, against_voucher, gle.party)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not voucher_balance:
|
|
||||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
|
||||||
voucher_balance = self.voucher_balance.get((gle.voucher_type, gle.voucher_no, gle.party))
|
|
||||||
|
|
||||||
return voucher_balance
|
|
||||||
|
|
||||||
def build_data(self):
|
def build_data(self):
|
||||||
# set outstanding for all the accumulated balances
|
# set outstanding for all the accumulated balances
|
||||||
# as we can use this to filter out invoices without outstanding
|
# as we can use this to filter out invoices without outstanding
|
||||||
@ -260,6 +240,7 @@ class ReceivablePayableReport(object):
|
|||||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||||
abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision
|
abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision
|
||||||
):
|
):
|
||||||
|
|
||||||
# non-zero oustanding, we must consider this row
|
# non-zero oustanding, we must consider this row
|
||||||
|
|
||||||
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
||||||
@ -669,48 +650,53 @@ class ReceivablePayableReport(object):
|
|||||||
index = 4
|
index = 4
|
||||||
row["range" + str(index + 1)] = row.outstanding
|
row["range" + str(index + 1)] = row.outstanding
|
||||||
|
|
||||||
def get_gl_entries(self):
|
def get_ple_entries(self):
|
||||||
# get all the GL entries filtered by the given filters
|
# get all the GL entries filtered by the given filters
|
||||||
|
|
||||||
conditions, values = self.prepare_conditions()
|
self.prepare_conditions()
|
||||||
order_by = self.get_order_by_condition()
|
|
||||||
|
|
||||||
if self.filters.show_future_payments:
|
if self.filters.show_future_payments:
|
||||||
values.insert(2, self.filters.report_date)
|
self.qb_selection_filter.append(
|
||||||
|
(
|
||||||
date_condition = """AND (posting_date <= %s
|
self.ple.posting_date.lte(self.filters.report_date)
|
||||||
OR (against_voucher IS NULL AND DATE(creation) <= %s))"""
|
| (
|
||||||
|
(self.ple.voucher_no == self.ple.against_voucher_no)
|
||||||
|
& (Date(self.ple.creation).lte(self.filters.report_date))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
date_condition = "AND posting_date <=%s"
|
self.qb_selection_filter.append(self.ple.posting_date.lte(self.filters.report_date))
|
||||||
|
|
||||||
if self.filters.get(scrub(self.party_type)):
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
|
query = (
|
||||||
else:
|
qb.from_(ple)
|
||||||
select_fields = "debit, credit"
|
.select(
|
||||||
|
ple.account,
|
||||||
doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
remarks = ", remarks" if self.filters.get("show_remarks") else ""
|
ple.against_voucher_type,
|
||||||
|
ple.against_voucher_no,
|
||||||
self.gl_entries = frappe.db.sql(
|
ple.party_type,
|
||||||
"""
|
ple.cost_center,
|
||||||
select
|
ple.party,
|
||||||
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
|
ple.posting_date,
|
||||||
against_voucher_type, against_voucher, account_currency, {0}, {1} {remarks}
|
ple.due_date,
|
||||||
from
|
ple.account_currency.as_("currency"),
|
||||||
`tabGL Entry`
|
ple.amount,
|
||||||
where
|
ple.amount_in_account_currency,
|
||||||
docstatus < 2
|
)
|
||||||
and is_cancelled = 0
|
.where(ple.delinked == 0)
|
||||||
and party_type=%s
|
.where(Criterion.all(self.qb_selection_filter))
|
||||||
and (party is not null and party != '')
|
|
||||||
{2} {3} {4}""".format(
|
|
||||||
select_fields, doc_currency_fields, date_condition, conditions, order_by, remarks=remarks
|
|
||||||
),
|
|
||||||
values,
|
|
||||||
as_dict=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.filters.get("group_by_party"):
|
||||||
|
query = query.orderby(self.ple.party, self.ple.posting_date)
|
||||||
|
else:
|
||||||
|
query = query.orderby(self.ple.posting_date, self.ple.party)
|
||||||
|
|
||||||
|
self.ple_entries = query.run(as_dict=True)
|
||||||
|
|
||||||
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
||||||
if self.filters.get("sales_person"):
|
if self.filters.get("sales_person"):
|
||||||
lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"])
|
||||||
@ -731,23 +717,21 @@ class ReceivablePayableReport(object):
|
|||||||
self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||||
|
|
||||||
def prepare_conditions(self):
|
def prepare_conditions(self):
|
||||||
conditions = [""]
|
self.qb_selection_filter = []
|
||||||
values = [self.party_type, self.filters.report_date]
|
|
||||||
party_type_field = scrub(self.party_type)
|
party_type_field = scrub(self.party_type)
|
||||||
|
|
||||||
self.add_common_filters(conditions, values, party_type_field)
|
self.add_common_filters(party_type_field=party_type_field)
|
||||||
|
|
||||||
if party_type_field == "customer":
|
if party_type_field == "customer":
|
||||||
self.add_customer_filters(conditions, values)
|
self.add_customer_filters()
|
||||||
|
|
||||||
elif party_type_field == "supplier":
|
elif party_type_field == "supplier":
|
||||||
self.add_supplier_filters(conditions, values)
|
self.add_supplier_filters()
|
||||||
|
|
||||||
if self.filters.cost_center:
|
if self.filters.cost_center:
|
||||||
self.get_cost_center_conditions(conditions)
|
self.get_cost_center_conditions()
|
||||||
|
|
||||||
self.add_accounting_dimensions_filters(conditions, values)
|
self.add_accounting_dimensions_filters()
|
||||||
return " and ".join(conditions), values
|
|
||||||
|
|
||||||
def get_cost_center_conditions(self, conditions):
|
def get_cost_center_conditions(self, conditions):
|
||||||
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
|
||||||
@ -755,32 +739,20 @@ class ReceivablePayableReport(object):
|
|||||||
center.name
|
center.name
|
||||||
for center in frappe.get_list("Cost Center", filters={"lft": (">=", lft), "rgt": ("<=", rgt)})
|
for center in frappe.get_list("Cost Center", filters={"lft": (">=", lft), "rgt": ("<=", rgt)})
|
||||||
]
|
]
|
||||||
|
self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list))
|
||||||
|
|
||||||
cost_center_string = '", "'.join(cost_center_list)
|
def add_common_filters(self, party_type_field):
|
||||||
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
|
|
||||||
|
|
||||||
def get_order_by_condition(self):
|
|
||||||
if self.filters.get("group_by_party"):
|
|
||||||
return "order by party, posting_date"
|
|
||||||
else:
|
|
||||||
return "order by posting_date, party"
|
|
||||||
|
|
||||||
def add_common_filters(self, conditions, values, party_type_field):
|
|
||||||
if self.filters.company:
|
if self.filters.company:
|
||||||
conditions.append("company=%s")
|
self.qb_selection_filter.append(self.ple.company == self.filters.company)
|
||||||
values.append(self.filters.company)
|
|
||||||
|
|
||||||
if self.filters.finance_book:
|
if self.filters.finance_book:
|
||||||
conditions.append("ifnull(finance_book, '') in (%s, '')")
|
self.qb_selection_filter.append(self.ple.finance_book == self.filters.finance_book)
|
||||||
values.append(self.filters.finance_book)
|
|
||||||
|
|
||||||
if self.filters.get(party_type_field):
|
if self.filters.get(party_type_field):
|
||||||
conditions.append("party=%s")
|
self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field))
|
||||||
values.append(self.filters.get(party_type_field))
|
|
||||||
|
|
||||||
if self.filters.party_account:
|
if self.filters.party_account:
|
||||||
conditions.append("account =%s")
|
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
||||||
values.append(self.filters.party_account)
|
|
||||||
else:
|
else:
|
||||||
# get GL with "receivable" or "payable" account_type
|
# get GL with "receivable" or "payable" account_type
|
||||||
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
|
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
|
||||||
@ -792,46 +764,68 @@ class ReceivablePayableReport(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
if accounts:
|
if accounts:
|
||||||
conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
|
self.qb_selection_filter.append(self.ple.account.isin(accounts))
|
||||||
values += accounts
|
|
||||||
|
def add_customer_filters(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
self.customter = qb.DocType("Customer")
|
||||||
|
|
||||||
def add_customer_filters(self, conditions, values):
|
|
||||||
if self.filters.get("customer_group"):
|
if self.filters.get("customer_group"):
|
||||||
conditions.append(self.get_hierarchical_filters("Customer Group", "customer_group"))
|
self.get_hierarchical_filters("Customer Group", "customer_group")
|
||||||
|
|
||||||
if self.filters.get("territory"):
|
if self.filters.get("territory"):
|
||||||
conditions.append(self.get_hierarchical_filters("Territory", "territory"))
|
self.get_hierarchical_filters("Territory", "territory")
|
||||||
|
|
||||||
if self.filters.get("payment_terms_template"):
|
if self.filters.get("payment_terms_template"):
|
||||||
conditions.append("party in (select name from tabCustomer where payment_terms=%s)")
|
self.qb_selection_filter.append(
|
||||||
values.append(self.filters.get("payment_terms_template"))
|
self.ple.party_isin(
|
||||||
|
qb.from_(self.customer).where(
|
||||||
|
self.customer.payment_terms == self.filters.get("payment_terms_template")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if self.filters.get("sales_partner"):
|
if self.filters.get("sales_partner"):
|
||||||
conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
|
self.qb_selection_filter.append(
|
||||||
values.append(self.filters.get("sales_partner"))
|
self.ple.party_isin(
|
||||||
|
qb.from_(self.customer).where(
|
||||||
def add_supplier_filters(self, conditions, values):
|
self.customer.default_sales_partner == self.filters.get("payment_terms_template")
|
||||||
if self.filters.get("supplier_group"):
|
)
|
||||||
conditions.append(
|
)
|
||||||
"""party in (select name from tabSupplier
|
)
|
||||||
where supplier_group=%s)"""
|
|
||||||
|
def add_supplier_filters(self):
|
||||||
|
supplier = qb.DocType("Supplier")
|
||||||
|
if self.filters.get("supplier_group"):
|
||||||
|
self.qb_selection_filter.append(
|
||||||
|
self.ple.party.isin(
|
||||||
|
qb.from_(supplier)
|
||||||
|
.select(supplier.name)
|
||||||
|
.where(supplier.supplier_group == self.filters.get("supplier_group"))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
values.append(self.filters.get("supplier_group"))
|
|
||||||
|
|
||||||
if self.filters.get("payment_terms_template"):
|
if self.filters.get("payment_terms_template"):
|
||||||
conditions.append("party in (select name from tabSupplier where payment_terms=%s)")
|
self.qb_selection_filter.append(
|
||||||
values.append(self.filters.get("payment_terms_template"))
|
self.ple.party.isin(
|
||||||
|
qb.from_(supplier)
|
||||||
|
.select(supplier.name)
|
||||||
|
.where(supplier.payment_terms == self.filters.get("supplier_group"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_hierarchical_filters(self, doctype, key):
|
def get_hierarchical_filters(self, doctype, key):
|
||||||
lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"])
|
||||||
|
|
||||||
return """party in (select name from tabCustomer
|
doc = qb.DocType(doctype)
|
||||||
where exists(select name from `tab{doctype}` where lft >= {lft} and rgt <= {rgt}
|
ple = self.ple
|
||||||
and name=tabCustomer.{key}))""".format(
|
customer = self.customer
|
||||||
doctype=doctype, lft=lft, rgt=rgt, key=key
|
groups = qb.from_(doc).select(doc.name).where((doc.lft >= lft) & (doc.rgt <= rgt))
|
||||||
)
|
customers = qb.from_(customer).select(customer.name).where(customer[key].isin(groups))
|
||||||
|
self.qb_selection_filter.append(ple.isin(ple.party.isin(customers)))
|
||||||
|
|
||||||
def add_accounting_dimensions_filters(self, conditions, values):
|
def add_accounting_dimensions_filters(self):
|
||||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||||
|
|
||||||
if accounting_dimensions:
|
if accounting_dimensions:
|
||||||
@ -841,30 +835,16 @@ class ReceivablePayableReport(object):
|
|||||||
self.filters[dimension.fieldname] = get_dimension_with_children(
|
self.filters[dimension.fieldname] = get_dimension_with_children(
|
||||||
dimension.document_type, self.filters.get(dimension.fieldname)
|
dimension.document_type, self.filters.get(dimension.fieldname)
|
||||||
)
|
)
|
||||||
conditions.append("{0} in %s".format(dimension.fieldname))
|
self.qb_selection_filter.append(
|
||||||
values.append(tuple(self.filters.get(dimension.fieldname)))
|
self.ple[dimension.fieldname].isin(self.filters[dimension.fieldname])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.qb_selection_filter.append(
|
||||||
|
self.ple[dimension.fieldname] == self.filters[dimension.fieldname]
|
||||||
|
)
|
||||||
|
|
||||||
def get_gle_balance(self, gle):
|
def is_invoice(self, ple):
|
||||||
# get the balance of the GL (debit - credit) or reverse balance based on report type
|
if ple.voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
||||||
return gle.get(self.dr_or_cr) - self.get_reverse_balance(gle)
|
|
||||||
|
|
||||||
def get_gle_balance_in_account_currency(self, gle):
|
|
||||||
# get the balance of the GL (debit - credit) or reverse balance based on report type
|
|
||||||
return gle.get(
|
|
||||||
self.dr_or_cr + "_in_account_currency"
|
|
||||||
) - self.get_reverse_balance_in_account_currency(gle)
|
|
||||||
|
|
||||||
def get_reverse_balance_in_account_currency(self, gle):
|
|
||||||
return gle.get(
|
|
||||||
"debit_in_account_currency" if self.dr_or_cr == "credit" else "credit_in_account_currency"
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_reverse_balance(self, gle):
|
|
||||||
# get "credit" balance if report type is "debit" and vice versa
|
|
||||||
return gle.get("debit" if self.dr_or_cr == "credit" else "credit")
|
|
||||||
|
|
||||||
def is_invoice(self, gle):
|
|
||||||
if gle.voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_party_details(self, party):
|
def get_party_details(self, party):
|
||||||
@ -926,9 +906,6 @@ class ReceivablePayableReport(object):
|
|||||||
width=180,
|
width=180,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.filters.show_remarks:
|
|
||||||
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
|
|
||||||
|
|
||||||
self.add_column(label="Due Date", fieldtype="Date")
|
self.add_column(label="Due Date", fieldtype="Date")
|
||||||
|
|
||||||
if self.party_type == "Supplier":
|
if self.party_type == "Supplier":
|
||||||
|
@ -12,6 +12,7 @@ class TestAccountsReceivable(unittest.TestCase):
|
|||||||
def test_accounts_receivable(self):
|
def test_accounts_receivable(self):
|
||||||
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
|
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
|
||||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
|
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
|
||||||
|
frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'")
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
"company": "_Test Company 2",
|
"company": "_Test Company 2",
|
||||||
|
@ -9,7 +9,10 @@ import frappe
|
|||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe import _, qb, throw
|
from frappe import _, qb, throw
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
|
from frappe.query_builder.utils import DocType
|
||||||
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
|
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
|
||||||
|
from pypika import Order
|
||||||
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
|
|
||||||
@ -42,37 +45,32 @@ def get_fiscal_years(
|
|||||||
|
|
||||||
if not fiscal_years:
|
if not fiscal_years:
|
||||||
# if year start date is 2012-04-01, year end date should be 2013-03-31 (hence subdate)
|
# if year start date is 2012-04-01, year end date should be 2013-03-31 (hence subdate)
|
||||||
cond = ""
|
FY = DocType("Fiscal Year")
|
||||||
if fiscal_year:
|
|
||||||
cond += " and fy.name = {0}".format(frappe.db.escape(fiscal_year))
|
|
||||||
if company:
|
|
||||||
cond += """
|
|
||||||
and (not exists (select name
|
|
||||||
from `tabFiscal Year Company` fyc
|
|
||||||
where fyc.parent = fy.name)
|
|
||||||
or exists(select company
|
|
||||||
from `tabFiscal Year Company` fyc
|
|
||||||
where fyc.parent = fy.name
|
|
||||||
and fyc.company=%(company)s)
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
fiscal_years = frappe.db.sql(
|
query = (
|
||||||
"""
|
frappe.qb.from_(FY)
|
||||||
select
|
.select(FY.name, FY.year_start_date, FY.year_end_date)
|
||||||
fy.name, fy.year_start_date, fy.year_end_date
|
.where(FY.disabled == 0)
|
||||||
from
|
|
||||||
`tabFiscal Year` fy
|
|
||||||
where
|
|
||||||
disabled = 0 {0}
|
|
||||||
order by
|
|
||||||
fy.year_start_date desc""".format(
|
|
||||||
cond
|
|
||||||
),
|
|
||||||
{"company": company},
|
|
||||||
as_dict=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if fiscal_year:
|
||||||
|
query = query.where(FY.name == fiscal_year)
|
||||||
|
|
||||||
|
if company:
|
||||||
|
FYC = DocType("Fiscal Year Company")
|
||||||
|
query = query.where(
|
||||||
|
ExistsCriterion(frappe.qb.from_(FYC).select(FYC.name).where(FYC.parent == FY.name)).negate()
|
||||||
|
| ExistsCriterion(
|
||||||
|
frappe.qb.from_(FYC)
|
||||||
|
.select(FYC.company)
|
||||||
|
.where(FYC.parent == FY.name)
|
||||||
|
.where(FYC.company == company)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
query = query.orderby(FY.year_start_date, Order.desc)
|
||||||
|
fiscal_years = query.run(as_dict=True)
|
||||||
|
|
||||||
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
||||||
|
|
||||||
if not transaction_date and not fiscal_year:
|
if not transaction_date and not fiscal_year:
|
||||||
|
@ -10,7 +10,7 @@ from frappe.utils.data import fmt_money
|
|||||||
from frappe.utils.jinja import render_template
|
from frappe.utils.jinja import render_template
|
||||||
from frappe.utils.pdf import get_pdf
|
from frappe.utils.pdf import get_pdf
|
||||||
from frappe.utils.print_format import read_multi_pdf
|
from frappe.utils.print_format import read_multi_pdf
|
||||||
from PyPDF2 import PdfFileWriter
|
from PyPDF2 import PdfWriter
|
||||||
|
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ def irs_1099_print(filters):
|
|||||||
|
|
||||||
columns, data = execute(filters)
|
columns, data = execute(filters)
|
||||||
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
|
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
|
||||||
output = PdfFileWriter()
|
output = PdfWriter()
|
||||||
|
|
||||||
for row in data:
|
for row in data:
|
||||||
row["fiscal_year"] = fiscal_year
|
row["fiscal_year"] = fiscal_year
|
||||||
|
@ -59,7 +59,6 @@ class TestExotel(FrappeAPITestCase):
|
|||||||
f"/api/method/erpnext.erpnext_integrations.exotel_integration.{api_method}",
|
f"/api/method/erpnext.erpnext_integrations.exotel_integration.{api_method}",
|
||||||
data=frappe.as_json(data),
|
data=frappe.as_json(data),
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
as_tuple=True,
|
|
||||||
)
|
)
|
||||||
# restart db connection to get latest data
|
# restart db connection to get latest data
|
||||||
frappe.connect()
|
frappe.connect()
|
||||||
|
@ -9908,5 +9908,5 @@ Delete Transactions,Supprimer les transactions
|
|||||||
Default Payment Discount Account,Compte par défaut des paiements de remise
|
Default Payment Discount Account,Compte par défaut des paiements de remise
|
||||||
Unrealized Profit / Loss Account,Compte de perte
|
Unrealized Profit / Loss Account,Compte de perte
|
||||||
Enable Provisional Accounting For Non Stock Items,Activer la provision pour les articles non stockés
|
Enable Provisional Accounting For Non Stock Items,Activer la provision pour les articles non stockés
|
||||||
Publish in Website=Publier sur le Site Web
|
Publish in Website,Publier sur le Site Web
|
||||||
List View,Vue en liste
|
List View,Vue en liste
|
||||||
|
Can't render this file because it is too large.
|
@ -1,46 +0,0 @@
|
|||||||
# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.utils.bot import BotParser
|
|
||||||
|
|
||||||
|
|
||||||
class FindItemBot(BotParser):
|
|
||||||
def get_reply(self):
|
|
||||||
if self.startswith("where is", "find item", "locate"):
|
|
||||||
if not frappe.has_permission("Warehouse"):
|
|
||||||
raise frappe.PermissionError
|
|
||||||
|
|
||||||
item = "%{0}%".format(self.strip_words(self.query, "where is", "find item", "locate"))
|
|
||||||
items = frappe.db.sql(
|
|
||||||
"""select name from `tabItem` where item_code like %(txt)s
|
|
||||||
or item_name like %(txt)s or description like %(txt)s""",
|
|
||||||
dict(txt=item),
|
|
||||||
)
|
|
||||||
|
|
||||||
if items:
|
|
||||||
out = []
|
|
||||||
warehouses = frappe.get_all("Warehouse")
|
|
||||||
for item in items:
|
|
||||||
found = False
|
|
||||||
for warehouse in warehouses:
|
|
||||||
qty = frappe.db.get_value(
|
|
||||||
"Bin", {"item_code": item[0], "warehouse": warehouse.name}, "actual_qty"
|
|
||||||
)
|
|
||||||
if qty:
|
|
||||||
out.append(
|
|
||||||
_("{0} units of [{1}](/app/Form/Item/{1}) found in [{2}](/app/Form/Warehouse/{2})").format(
|
|
||||||
qty, item[0], warehouse.name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
found = True
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
out.append(_("[{0}](/app/Form/Item/{0}) is out of stock").format(item[0]))
|
|
||||||
|
|
||||||
return "\n\n".join(out)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return _("Did not find any item called {0}").format(item)
|
|
Loading…
Reference in New Issue
Block a user