Merge branch 'develop' into subcontract-supplied-items
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index afabe43..2cf4444 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -115,4 +115,5 @@
echo "Updating to latest version"
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
+ bench setup requirements --python
bench --site test_site migrate
diff --git a/.mergify.yml b/.mergify.yml
index cc8c080..d7f82e6 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -9,6 +9,8 @@
- author!=nabinhait
- author!=ankush
- author!=deepeshgarg007
+ - author!=mergify[bot]
+
- or:
- base=version-13
- base=version-12
@@ -19,6 +21,16 @@
@{{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
+ - 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
conditions:
- label="backport develop"
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 748bcde..0238711 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -173,11 +173,6 @@
"fieldtype": "Check",
},
{
- "fieldname": "show_remarks",
- "label": __("Show Remarks"),
- "fieldtype": "Check",
- },
- {
"fieldname": "tax_id",
"label": __("Tax Id"),
"fieldtype": "Data",
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index de9d63d..1911152 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -5,7 +5,9 @@
from collections import OrderedDict
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 erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -41,6 +43,8 @@
class ReceivablePayableReport(object):
def __init__(self, filters=None):
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.age_as_on = (
getdate(nowdate())
@@ -78,7 +82,7 @@
self.skip_total_row = 1
def get_data(self):
- self.get_gl_entries()
+ self.get_ple_entries()
self.get_sales_invoices_or_customers_based_on_sales_person()
self.voucher_balance = OrderedDict()
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
@@ -96,25 +100,25 @@
self.get_return_entries()
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()
def init_voucher_balance(self):
# 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
- 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:
self.voucher_balance[key] = frappe._dict(
- voucher_type=gle.voucher_type,
- voucher_no=gle.voucher_no,
- party=gle.party,
- party_account=gle.account,
- posting_date=gle.posting_date,
- account_currency=gle.account_currency,
- remarks=gle.remarks if self.filters.get("show_remarks") else None,
+ voucher_type=ple.voucher_type,
+ voucher_no=ple.voucher_no,
+ party=ple.party,
+ party_account=ple.account,
+ posting_date=ple.posting_date,
+ account_currency=ple.account_currency,
invoiced=0.0,
paid=0.0,
credit_note=0.0,
@@ -124,23 +128,22 @@
credit_note_in_account_currency=0.0,
outstanding_in_account_currency=0.0,
)
- self.get_invoices(gle)
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"):
self.init_subtotal_row("Total")
- def get_invoices(self, gle):
- if gle.voucher_type in ("Sales Invoice", "Purchase Invoice"):
+ def get_invoices(self, ple):
+ if ple.voucher_type in ("Sales Invoice", "Purchase Invoice"):
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", []
- ) or gle.party in self.sales_person_records.get("Customer", []):
- self.invoices.add(gle.voucher_no)
+ ) or ple.party in self.sales_person_records.get("Customer", []):
+ self.invoices.add(ple.voucher_no)
else:
- self.invoices.add(gle.voucher_no)
+ self.invoices.add(ple.voucher_no)
def init_subtotal_row(self, party):
if not self.total_row_map.get(party):
@@ -162,39 +165,49 @@
"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
# 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:
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:
- if gle.voucher_type in ("Journal Entry", "Payment Entry") and gle.against_voucher:
- # debit against sales / purchase invoice
- row.paid -= gle_balance
- row.paid_in_account_currency -= gle_balance_in_account_currency
+ amount = ple.amount
+ amount_in_account_currency = ple.amount_in_account_currency
+
+ # update voucher
+ 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:
- # invoice
- row.invoiced += gle_balance
- row.invoiced_in_account_currency += gle_balance_in_account_currency
+ row.invoiced += amount
+ row.invoiced_in_account_currency += amount_in_account_currency
else:
- # payment or credit note for receivables
- if self.is_invoice(gle):
- # stand alone debit / credit note
- row.credit_note -= gle_balance
- row.credit_note_in_account_currency -= gle_balance_in_account_currency
+ if self.is_invoice(ple):
+ row.credit_note -= amount
+ row.credit_note_in_account_currency -= amount_in_account_currency
else:
- # advance / unlinked payment or other adjustment
- row.paid -= gle_balance
- row.paid_in_account_currency -= gle_balance_in_account_currency
+ row.paid -= amount
+ row.paid_in_account_currency -= amount_in_account_currency
- if gle.cost_center:
- row.cost_center = str(gle.cost_center)
+ if ple.cost_center:
+ row.cost_center = str(ple.cost_center)
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
@@ -210,39 +223,6 @@
self.data.append({})
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):
# set outstanding for all the accumulated balances
# as we can use this to filter out invoices without outstanding
@@ -260,6 +240,7 @@
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision
):
+
# non-zero oustanding, we must consider this row
if self.is_invoice(row) and self.filters.based_on_payment_terms:
@@ -669,48 +650,53 @@
index = 4
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
- conditions, values = self.prepare_conditions()
- order_by = self.get_order_by_condition()
+ self.prepare_conditions()
if self.filters.show_future_payments:
- values.insert(2, self.filters.report_date)
-
- date_condition = """AND (posting_date <= %s
- OR (against_voucher IS NULL AND DATE(creation) <= %s))"""
+ self.qb_selection_filter.append(
+ (
+ self.ple.posting_date.lte(self.filters.report_date)
+ | (
+ (self.ple.voucher_no == self.ple.against_voucher_no)
+ & (Date(self.ple.creation).lte(self.filters.report_date))
+ )
+ )
+ )
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)):
- select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
- else:
- select_fields = "debit, credit"
-
- doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
-
- remarks = ", remarks" if self.filters.get("show_remarks") else ""
-
- self.gl_entries = frappe.db.sql(
- """
- select
- name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
- against_voucher_type, against_voucher, account_currency, {0}, {1} {remarks}
- from
- `tabGL Entry`
- where
- docstatus < 2
- and is_cancelled = 0
- and party_type=%s
- 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,
+ ple = qb.DocType("Payment Ledger Entry")
+ query = (
+ qb.from_(ple)
+ .select(
+ ple.account,
+ ple.voucher_type,
+ ple.voucher_no,
+ ple.against_voucher_type,
+ ple.against_voucher_no,
+ ple.party_type,
+ ple.cost_center,
+ ple.party,
+ ple.posting_date,
+ ple.due_date,
+ ple.account_currency.as_("currency"),
+ ple.amount,
+ ple.amount_in_account_currency,
+ )
+ .where(ple.delinked == 0)
+ .where(Criterion.all(self.qb_selection_filter))
)
+ 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):
if self.filters.get("sales_person"):
lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"])
@@ -731,23 +717,21 @@
self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
def prepare_conditions(self):
- conditions = [""]
- values = [self.party_type, self.filters.report_date]
+ self.qb_selection_filter = []
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":
- self.add_customer_filters(conditions, values)
+ self.add_customer_filters()
elif party_type_field == "supplier":
- self.add_supplier_filters(conditions, values)
+ self.add_supplier_filters()
if self.filters.cost_center:
- self.get_cost_center_conditions(conditions)
+ self.get_cost_center_conditions()
- self.add_accounting_dimensions_filters(conditions, values)
- return " and ".join(conditions), values
+ self.add_accounting_dimensions_filters()
def get_cost_center_conditions(self, conditions):
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
@@ -755,32 +739,20 @@
center.name
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)
- 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):
+ def add_common_filters(self, party_type_field):
if self.filters.company:
- conditions.append("company=%s")
- values.append(self.filters.company)
+ self.qb_selection_filter.append(self.ple.company == self.filters.company)
if self.filters.finance_book:
- conditions.append("ifnull(finance_book, '') in (%s, '')")
- values.append(self.filters.finance_book)
+ self.qb_selection_filter.append(self.ple.finance_book == self.filters.finance_book)
if self.filters.get(party_type_field):
- conditions.append("party=%s")
- values.append(self.filters.get(party_type_field))
+ self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field))
if self.filters.party_account:
- conditions.append("account =%s")
- values.append(self.filters.party_account)
+ self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
else:
# get GL with "receivable" or "payable" account_type
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
@@ -792,46 +764,68 @@
]
if accounts:
- conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
- values += accounts
+ self.qb_selection_filter.append(self.ple.account.isin(accounts))
- def add_customer_filters(self, conditions, values):
+ def add_customer_filters(
+ self,
+ ):
+ self.customter = qb.DocType("Customer")
+
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"):
- conditions.append(self.get_hierarchical_filters("Territory", "territory"))
+ self.get_hierarchical_filters("Territory", "territory")
if self.filters.get("payment_terms_template"):
- conditions.append("party in (select name from tabCustomer where payment_terms=%s)")
- values.append(self.filters.get("payment_terms_template"))
+ self.qb_selection_filter.append(
+ 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"):
- conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
- values.append(self.filters.get("sales_partner"))
-
- def add_supplier_filters(self, conditions, values):
- if self.filters.get("supplier_group"):
- conditions.append(
- """party in (select name from tabSupplier
- where supplier_group=%s)"""
+ self.qb_selection_filter.append(
+ self.ple.party_isin(
+ qb.from_(self.customer).where(
+ self.customer.default_sales_partner == self.filters.get("payment_terms_template")
+ )
+ )
)
- values.append(self.filters.get("supplier_group"))
+
+ 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"))
+ )
+ )
if self.filters.get("payment_terms_template"):
- conditions.append("party in (select name from tabSupplier where payment_terms=%s)")
- values.append(self.filters.get("payment_terms_template"))
+ self.qb_selection_filter.append(
+ 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):
lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"])
- return """party in (select name from tabCustomer
- where exists(select name from `tab{doctype}` where lft >= {lft} and rgt <= {rgt}
- and name=tabCustomer.{key}))""".format(
- doctype=doctype, lft=lft, rgt=rgt, key=key
- )
+ doc = qb.DocType(doctype)
+ ple = self.ple
+ customer = self.customer
+ 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)
if accounting_dimensions:
@@ -841,30 +835,16 @@
self.filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, self.filters.get(dimension.fieldname)
)
- conditions.append("{0} in %s".format(dimension.fieldname))
- values.append(tuple(self.filters.get(dimension.fieldname)))
+ self.qb_selection_filter.append(
+ 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):
- # get the balance of the GL (debit - credit) or reverse balance based on report type
- 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"):
+ def is_invoice(self, ple):
+ if ple.voucher_type in ("Sales Invoice", "Purchase Invoice"):
return True
def get_party_details(self, party):
@@ -926,9 +906,6 @@
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")
if self.party_type == "Supplier":
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index f38890e..edddbbc 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -12,6 +12,7 @@
def test_accounts_receivable(self):
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
+ frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'")
filters = {
"company": "_Test Company 2",
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 8711395..65e0541 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -9,7 +9,10 @@
import frappe.defaults
from frappe import _, qb, throw
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 pypika import Order
+from pypika.terms import ExistsCriterion
import erpnext
@@ -42,37 +45,32 @@
if not fiscal_years:
# if year start date is 2012-04-01, year end date should be 2013-03-31 (hence subdate)
- cond = ""
- 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)
- )
- """
+ FY = DocType("Fiscal Year")
- fiscal_years = frappe.db.sql(
- """
- select
- fy.name, fy.year_start_date, fy.year_end_date
- from
- `tabFiscal Year` fy
- where
- disabled = 0 {0}
- order by
- fy.year_start_date desc""".format(
- cond
- ),
- {"company": company},
- as_dict=True,
+ query = (
+ frappe.qb.from_(FY)
+ .select(FY.name, FY.year_start_date, FY.year_end_date)
+ .where(FY.disabled == 0)
)
+ 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)
if not transaction_date and not fiscal_year:
diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py
index 92aeb5e..0f578be 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.py
+++ b/erpnext/regional/report/irs_1099/irs_1099.py
@@ -10,7 +10,7 @@
from frappe.utils.jinja import render_template
from frappe.utils.pdf import get_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
@@ -106,7 +106,7 @@
columns, data = execute(filters)
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
- output = PdfFileWriter()
+ output = PdfWriter()
for row in data:
row["fiscal_year"] = fiscal_year
diff --git a/erpnext/tests/test_exotel.py b/erpnext/tests/test_exotel.py
index 76bbb3e..31baf75 100644
--- a/erpnext/tests/test_exotel.py
+++ b/erpnext/tests/test_exotel.py
@@ -59,7 +59,6 @@
f"/api/method/erpnext.erpnext_integrations.exotel_integration.{api_method}",
data=frappe.as_json(data),
content_type="application/json",
- as_tuple=True,
)
# restart db connection to get latest data
frappe.connect()
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 40fb5ad..ffc46d2 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -9908,5 +9908,5 @@
Default Payment Discount Account,Compte par défaut des paiements de remise
Unrealized Profit / Loss Account,Compte de perte
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
diff --git a/erpnext/utilities/bot.py b/erpnext/utilities/bot.py
deleted file mode 100644
index 5c2e576..0000000
--- a/erpnext/utilities/bot.py
+++ /dev/null
@@ -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)