Merge branch 'develop' of https://github.com/frappe/erpnext into quotation_issues
This commit is contained in:
commit
5b2ba087a3
1
.github/workflows/patch.yml
vendored
1
.github/workflows/patch.yml
vendored
@ -115,4 +115,5 @@ jobs:
|
||||
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
|
||||
|
12
.mergify.yml
12
.mergify.yml
@ -9,6 +9,8 @@ pull_request_rules:
|
||||
- author!=nabinhait
|
||||
- author!=ankush
|
||||
- author!=deepeshgarg007
|
||||
- author!=mergify[bot]
|
||||
|
||||
- or:
|
||||
- base=version-13
|
||||
- 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.
|
||||
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"
|
||||
|
@ -322,9 +322,9 @@ def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select name from tabAccount
|
||||
where is_group = 1 and docstatus != 2 and company = %s
|
||||
and %s like %s order by name limit %s, %s"""
|
||||
and %s like %s order by name limit %s offset %s"""
|
||||
% ("%s", searchfield, "%s", "%s", "%s"),
|
||||
(filters["company"], "%%%s%%" % txt, start, page_len),
|
||||
(filters["company"], "%%%s%%" % txt, page_len, start),
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
|
@ -1239,7 +1239,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
AND jv.docstatus = 1
|
||||
AND jv.`{0}` LIKE %(txt)s
|
||||
ORDER BY jv.name DESC
|
||||
LIMIT %(offset)s, %(limit)s
|
||||
LIMIT %(limit)s offset %(offset)s
|
||||
""".format(
|
||||
searchfield
|
||||
),
|
||||
|
@ -39,7 +39,7 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
""" select mode_of_payment from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and mode_of_payment like %(txt)s
|
||||
limit %(start)s, %(page_len)s""",
|
||||
limit %(page_len)s offset %(start)s""",
|
||||
{"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
|
||||
)
|
||||
|
||||
@ -51,7 +51,7 @@ def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
""" select supplier from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and supplier like %(txt)s and
|
||||
(payment_reference is null or payment_reference='')
|
||||
limit %(start)s, %(page_len)s""",
|
||||
limit %(page_len)s offset %(start)s""",
|
||||
{"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
|
||||
)
|
||||
|
||||
|
@ -173,7 +173,7 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
where
|
||||
pfu.parent = pf.name and pfu.user = %(user)s and pf.company = %(company)s
|
||||
and (pf.name like %(txt)s)
|
||||
and pf.disabled = 0 limit %(start)s, %(page_len)s""",
|
||||
and pf.disabled = 0 limit %(page_len)s offset %(start)s""",
|
||||
args,
|
||||
)
|
||||
|
||||
|
@ -172,11 +172,6 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"label": __("Show Sales Person"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_id",
|
||||
"label": __("Tax Id"),
|
||||
|
@ -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 @@ def execute(filters=None):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
"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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
]
|
||||
|
||||
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,
|
||||
):
|
||||
self.customter = qb.DocType("Customer")
|
||||
|
||||
def add_customer_filters(self, conditions, values):
|
||||
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")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
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"):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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 @@ class ReceivablePayableReport(object):
|
||||
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":
|
||||
|
@ -12,6 +12,7 @@ class TestAccountsReceivable(unittest.TestCase):
|
||||
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",
|
||||
|
@ -9,7 +9,10 @@ import frappe
|
||||
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 @@ def get_fiscal_years(
|
||||
|
||||
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:
|
||||
|
@ -285,7 +285,7 @@ def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
|
||||
"""select `tabContact`.name from `tabContact`, `tabDynamic Link`
|
||||
where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
|
||||
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
|
||||
limit %(start)s, %(page_len)s""",
|
||||
limit %(page_len)s offset %(start)s""",
|
||||
{"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")},
|
||||
)
|
||||
|
||||
|
@ -33,7 +33,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
|
||||
idx desc,
|
||||
name, employee_name
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{
|
||||
"fields": ", ".join(fields),
|
||||
"key": searchfield,
|
||||
@ -65,7 +65,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, company_name), locate(%(_txt)s, company_name), 99999),
|
||||
idx desc,
|
||||
name, lead_name
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{"fields": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
@ -100,7 +100,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, customer_name), locate(%(_txt)s, customer_name), 99999),
|
||||
idx desc,
|
||||
name, customer_name
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{
|
||||
"fields": ", ".join(fields),
|
||||
"scond": searchfields,
|
||||
@ -137,7 +137,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, supplier_name), locate(%(_txt)s, supplier_name), 99999),
|
||||
idx desc,
|
||||
name, supplier_name
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
@ -167,7 +167,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
AND `{searchfield}` LIKE %(txt)s
|
||||
{mcond}
|
||||
ORDER BY idx DESC, name
|
||||
LIMIT %(offset)s, %(limit)s
|
||||
LIMIT %(limit)s offset %(offset)s
|
||||
""".format(
|
||||
account_type_condition=account_type_condition,
|
||||
searchfield=searchfield,
|
||||
@ -351,7 +351,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
idx desc,
|
||||
`tabProject`.name asc
|
||||
limit {start}, {page_len}""".format(
|
||||
limit {page_len} offset {start}""".format(
|
||||
fields=", ".join(["`tabProject`.{0}".format(f) for f in fields]),
|
||||
cond=cond,
|
||||
scond=searchfields,
|
||||
@ -383,7 +383,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
|
||||
)
|
||||
)
|
||||
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
|
||||
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(page_len)s offset %(start)s
|
||||
"""
|
||||
% {
|
||||
"fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
|
||||
@ -456,7 +456,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
{match_conditions}
|
||||
group by batch_no {having_clause}
|
||||
order by batch.expiry_date, sle.batch_no desc
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
search_columns=search_columns,
|
||||
cond=cond,
|
||||
match_conditions=get_match_cond(doctype),
|
||||
@ -483,7 +483,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
{match_conditions}
|
||||
|
||||
order by expiry_date, name desc
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
cond,
|
||||
search_columns=search_columns,
|
||||
search_cond=search_cond,
|
||||
@ -662,7 +662,7 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
{fcond} {mcond}
|
||||
order by ifnull(`tabBin`.actual_qty, 0) desc
|
||||
limit
|
||||
{start}, {page_len}
|
||||
{page_len} offset {start}
|
||||
""".format(
|
||||
bin_conditions=get_filters_cond(
|
||||
doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
|
||||
|
@ -102,7 +102,7 @@ def get_work_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select name from `tabWork Order`
|
||||
where name like %(name)s and {0} and produced_qty > qty and docstatus = 1
|
||||
order by name limit {1}, {2}""".format(
|
||||
order by name limit {2} offset {1}""".format(
|
||||
cond, start, page_len
|
||||
),
|
||||
{"name": "%%%s%%" % txt},
|
||||
|
@ -970,7 +970,7 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
|
||||
and name not in
|
||||
(select reference_name from `tabJournal Entry Account`
|
||||
where reference_type="Payroll Entry")
|
||||
order by name limit %(start)s, %(page_len)s""".format(
|
||||
order by name limit %(page_len)s offset %(start)s""".format(
|
||||
key=searchfield
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "start": start, "page_len": page_len},
|
||||
@ -1039,7 +1039,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
|
||||
idx desc,
|
||||
name, employee_name
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{
|
||||
"key": searchfield,
|
||||
"fcond": get_filters_cond(doctype, filters, conditions),
|
||||
|
@ -391,7 +391,7 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, full_name), locate(%(_txt)s, full_name), 99999),
|
||||
idx desc,
|
||||
name, full_name
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{
|
||||
"key": searchfield,
|
||||
"fcond": get_filters_cond(doctype, filters, conditions),
|
||||
|
@ -288,7 +288,7 @@ def get_project(doctype, txt, searchfield, start, page_len, filters):
|
||||
%(mcond)s
|
||||
{search_condition}
|
||||
order by name
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
search_columns=search_columns, search_condition=search_cond
|
||||
),
|
||||
{
|
||||
|
@ -328,7 +328,7 @@ def get_timesheet(doctype, txt, searchfield, start, page_len, filters):
|
||||
ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and
|
||||
tsd.docstatus = 1 and ts.total_billable_amount > 0
|
||||
and tsd.parent LIKE %(txt)s {condition}
|
||||
order by tsd.parent limit %(start)s, %(page_len)s""".format(
|
||||
order by tsd.parent limit %(page_len)s offset %(start)s""".format(
|
||||
condition=condition
|
||||
),
|
||||
{
|
||||
@ -515,7 +515,7 @@ def get_timesheets_list(
|
||||
tsd.project IN %(projects)s
|
||||
)
|
||||
ORDER BY `end_date` ASC
|
||||
LIMIT {0}, {1}
|
||||
LIMIT {1} offset {0}
|
||||
""".format(
|
||||
limit_start, limit_page_length
|
||||
),
|
||||
|
@ -25,7 +25,7 @@ def query_task(doctype, txt, searchfield, start, page_len, filters):
|
||||
case when `%s` like %s then 0 else 1 end,
|
||||
`%s`,
|
||||
subject
|
||||
limit %s, %s"""
|
||||
limit %s offset %s"""
|
||||
% (searchfield, "%s", "%s", match_conditions, "%s", searchfield, "%s", searchfield, "%s", "%s"),
|
||||
(search_string, search_string, order_by_string, order_by_string, start, page_len),
|
||||
(search_string, search_string, order_by_string, order_by_string, page_len, start),
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ from frappe.utils.data import fmt_money
|
||||
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 @@ def irs_1099_print(filters):
|
||||
|
||||
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
|
||||
|
@ -78,7 +78,7 @@ def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select name, item_name, description from tabItem
|
||||
where is_stock_item=0 and name not in (select name from `tabProduct Bundle`)
|
||||
and %s like %s %s limit %s, %s"""
|
||||
and %s like %s %s limit %s offset %s"""
|
||||
% (searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
|
||||
("%%%s%%" % txt, start, page_len),
|
||||
("%%%s%%" % txt, page_len, start),
|
||||
)
|
||||
|
@ -107,7 +107,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te
|
||||
ORDER BY
|
||||
item.name asc
|
||||
LIMIT
|
||||
{start}, {page_length}""".format(
|
||||
{page_length} offset {start}""".format(
|
||||
start=start,
|
||||
page_length=page_length,
|
||||
lft=lft,
|
||||
@ -204,7 +204,7 @@ def item_group_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
return frappe.db.sql(
|
||||
""" select distinct name from `tabItem Group`
|
||||
where {condition} and (name like %(txt)s) limit {start}, {page_len}""".format(
|
||||
where {condition} and (name like %(txt)s) limit {page_len} offset {start}""".format(
|
||||
condition=cond, start=start, page_len=page_len
|
||||
),
|
||||
{"txt": "%%%s%%" % txt},
|
||||
|
@ -21,7 +21,7 @@ def get_party_type(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select name from `tabParty Type`
|
||||
where `{key}` LIKE %(txt)s {cond}
|
||||
order by name limit %(start)s, %(page_len)s""".format(
|
||||
order by name limit %(page_len)s offset %(start)s""".format(
|
||||
key=searchfield, cond=cond
|
||||
),
|
||||
{"txt": "%" + txt + "%", "start": start, "page_len": page_len},
|
||||
|
@ -77,7 +77,7 @@ def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
|
||||
union
|
||||
(select item_code from `tabItem Alternative`
|
||||
where alternative_item_code = %(item_code)s and item_code like %(txt)s
|
||||
and two_way = 1) limit {0}, {1}
|
||||
and two_way = 1) limit {1} offset {0}
|
||||
""".format(
|
||||
start, page_len
|
||||
),
|
||||
|
@ -203,7 +203,7 @@ def item_details(doctype, txt, searchfield, start, page_len, filters):
|
||||
where name in ( select item_code FROM `tabDelivery Note Item`
|
||||
where parent= %s)
|
||||
and %s like "%s" %s
|
||||
limit %s, %s """
|
||||
limit %s offset %s """
|
||||
% ("%s", searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
|
||||
((filters or {}).get("delivery_note"), "%%%s%%" % txt, start, page_len),
|
||||
((filters or {}).get("delivery_note"), "%%%s%%" % txt, page_len, start),
|
||||
)
|
||||
|
@ -232,7 +232,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
FROM `tab{doc}`
|
||||
WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
|
||||
{qi_condition} {cond} {mcond}
|
||||
ORDER BY item_code limit {start}, {page_len}
|
||||
ORDER BY item_code limit {page_len} offset {start}
|
||||
""".format(
|
||||
doc=filters.get("from"),
|
||||
cond=cond,
|
||||
@ -252,7 +252,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s
|
||||
{qi_condition} {cond} {mcond}
|
||||
ORDER BY production_item
|
||||
LIMIT {start}, {page_len}
|
||||
limit {page_len} offset {start}
|
||||
""".format(
|
||||
doc=filters.get("from"),
|
||||
cond=cond,
|
||||
|
@ -1980,23 +1980,30 @@ class StockEntry(StockController):
|
||||
):
|
||||
|
||||
# Get PO Supplied Items Details
|
||||
item_wh = frappe._dict(
|
||||
frappe.db.sql(
|
||||
"""
|
||||
select rm_item_code, reserve_warehouse
|
||||
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
|
||||
where po.name = poitemsup.parent
|
||||
and po.name = %s""",
|
||||
self.purchase_order,
|
||||
)
|
||||
po_supplied_items = frappe.db.get_all(
|
||||
"Purchase Order Item Supplied",
|
||||
filters={"parent": self.purchase_order},
|
||||
fields=["name", "rm_item_code", "reserve_warehouse"],
|
||||
)
|
||||
|
||||
# Get Items Supplied in Stock Entries against PO
|
||||
supplied_items = get_supplied_items(self.purchase_order)
|
||||
for name, item in supplied_items.items():
|
||||
frappe.db.set_value("Purchase Order Item Supplied", name, item)
|
||||
|
||||
# Update reserved sub contracted quantity in bin based on Supplied Item Details and
|
||||
for row in po_supplied_items:
|
||||
key, item = row.name, {}
|
||||
if not supplied_items.get(key):
|
||||
# no stock transferred against PO Supplied Items row
|
||||
item = {"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0}
|
||||
else:
|
||||
item = supplied_items.get(key)
|
||||
|
||||
frappe.db.set_value("Purchase Order Item Supplied", row.name, item)
|
||||
|
||||
# RM Item-Reserve Warehouse Dict
|
||||
item_wh = {x.get("rm_item_code"): x.get("reserve_warehouse") for x in po_supplied_items}
|
||||
|
||||
for d in self.get("items"):
|
||||
# Update reserved sub contracted quantity in bin based on Supplied Item Details and
|
||||
item_code = d.get("original_item") or d.get("item_code")
|
||||
reserve_warehouse = item_wh.get(item_code)
|
||||
if not (reserve_warehouse and item_code):
|
||||
|
@ -56,7 +56,10 @@ def get_product_data(search=None, start=0, limit=12):
|
||||
search = "%" + cstr(search) + "%"
|
||||
|
||||
# order by
|
||||
query += """ ORDER BY ranking desc, modified desc limit %s, %s""" % (cint(start), cint(limit))
|
||||
query += """ ORDER BY ranking desc, modified desc limit %s offset %s""" % (
|
||||
cint(limit),
|
||||
cint(start),
|
||||
)
|
||||
|
||||
return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep
|
||||
|
||||
|
@ -59,7 +59,6 @@ class TestExotel(FrappeAPITestCase):
|
||||
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()
|
||||
|
@ -879,6 +879,55 @@ class TestSubcontracting(unittest.TestCase):
|
||||
for key, value in get_supplied_items(pr1).items():
|
||||
self.assertEqual(value.qty, 2)
|
||||
|
||||
def test_po_supplied_qty(self):
|
||||
"""
|
||||
Check if 'Supplied Qty' in PO's Supplied Items table is reset on submit/cancel.
|
||||
"""
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Item SA1",
|
||||
"qty": 5,
|
||||
"rate": 100,
|
||||
},
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Item SA5",
|
||||
"qty": 6,
|
||||
"rate": 100,
|
||||
},
|
||||
]
|
||||
|
||||
rm_items = [
|
||||
{"item_code": "Subcontracted SRM Item 1", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
|
||||
{"item_code": "Subcontracted SRM Item 2", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
|
||||
{"item_code": "Subcontracted SRM Item 3", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
|
||||
{"item_code": "Subcontracted SRM Item 5", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
|
||||
{"item_code": "Subcontracted SRM Item 4", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
|
||||
]
|
||||
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
po = create_purchase_order(
|
||||
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||
)
|
||||
|
||||
for d in rm_items:
|
||||
d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
|
||||
|
||||
se = make_stock_transfer_entry(
|
||||
po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
|
||||
)
|
||||
|
||||
po.reload()
|
||||
for row in po.get("supplied_items"):
|
||||
self.assertIn(row.supplied_qty, [5.0, 6.0])
|
||||
|
||||
se.cancel()
|
||||
po.reload()
|
||||
for row in po.get("supplied_items"):
|
||||
self.assertEqual(row.supplied_qty, 0.0)
|
||||
|
||||
|
||||
def add_second_row_in_pr(pr):
|
||||
item_dict = {}
|
||||
|
@ -1352,11 +1352,11 @@ Item Description,Description de l'Article,
|
||||
Item Group,Groupe d'Article,
|
||||
Item Group Tree,Arborescence de Groupe d'Article,
|
||||
Item Group not mentioned in item master for item {0},Le Groupe d'Articles n'est pas mentionné dans la fiche de l'article pour l'article {0},
|
||||
Item Name,Nom de l'article,
|
||||
Item Name,Nom de l'article,
|
||||
Item Price added for {0} in Price List {1},Prix de l'Article ajouté pour {0} dans la Liste de Prix {1},
|
||||
"Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates.","Le prix de l'article apparaît plusieurs fois en fonction de la liste de prix, du fournisseur / client, de la devise, de l'article, de l'unité de mesure, de la quantité et des dates.",
|
||||
"Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates.","Le prix de l'article apparaît plusieurs fois en fonction de la liste de prix, du fournisseur / client, de la devise, de l'article, de l'unité de mesure, de la quantité et des dates.",
|
||||
Item Price updated for {0} in Price List {1},Prix de l'Article mis à jour pour {0} dans la Liste des Prix {1},
|
||||
Item Row {0}: {1} {2} does not exist in above '{1}' table,Ligne d'objet {0}: {1} {2} n'existe pas dans la table '{1}' ci-dessus,
|
||||
Item Row {0}: {1} {2} does not exist in above '{1}' table,Ligne d'objet {0}: {1} {2} n'existe pas dans la table '{1}' ci-dessus,
|
||||
Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable,La Ligne de Taxe d'Article {0} doit indiquer un compte de type Taxes ou Produit ou Charge ou Facturable,
|
||||
Item Template,Modèle d'article,
|
||||
Item Variant Settings,Paramètres de Variante d'Article,
|
||||
@ -3661,7 +3661,7 @@ Chart,Graphique,
|
||||
Choose a corresponding payment,Choisissez un paiement correspondant,
|
||||
Click on the link below to verify your email and confirm the appointment,Cliquez sur le lien ci-dessous pour vérifier votre email et confirmer le rendez-vous,
|
||||
Close,Fermer,
|
||||
Communication,la communication,
|
||||
Communication,Communication,
|
||||
Compact Item Print,Impression de l'Article Compacté,
|
||||
Company,Société,
|
||||
Company of asset {0} and purchase document {1} doesn't matches.,La société de l'actif {0} et le document d'achat {1} ne correspondent pas.,
|
||||
@ -3969,7 +3969,7 @@ Quantity to Manufacture can not be zero for the operation {0},La quantité à fa
|
||||
Quarterly,Trimestriel,
|
||||
Queued,File d'Attente,
|
||||
Quick Entry,Écriture Rapide,
|
||||
Quiz {0} does not exist,Le questionnaire {0} n'existe pas,
|
||||
Quiz {0} does not exist,Le questionnaire {0} n'existe pas,
|
||||
Quotation Amount,Montant du devis,
|
||||
Rate or Discount is required for the price discount.,Le taux ou la remise est requis pour la remise de prix.,
|
||||
Reason,Raison,
|
||||
@ -4071,7 +4071,7 @@ Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and
|
||||
Stores - {0},Magasins - {0},
|
||||
Student with email {0} does not exist,Étudiant avec le courrier électronique {0} n'existe pas,
|
||||
Submit Review,Poster un commentaire,
|
||||
Submitted,Soumis,
|
||||
Submitted,Valider,
|
||||
Supplier Addresses And Contacts,Adresses et contacts des fournisseurs,
|
||||
Synchronize this account,Synchroniser ce compte,
|
||||
Tag,Étiquette,
|
||||
@ -9871,8 +9871,42 @@ Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les t
|
||||
Convert Item Description to Clean HTML in Transactions,Convertir les descriptions d'articles en HTML valide lors des transactions
|
||||
Have Default Naming Series for Batch ID?,Nom de série par défaut pour les Lots ou Séries
|
||||
"The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units","Le pourcentage de quantité que vous pourrez réceptionner en plus de la quantité commandée. Par exemple, vous avez commandé 100 unités, votre pourcentage de dépassement est de 10%, vous pourrez réceptionner 110 unités"
|
||||
Unit Of Measure (UOM),Unité de mesure (UDM),
|
||||
Allowed Items,Articles autorisés
|
||||
Party Specific Item,Restriction d'article disponible
|
||||
Restrict Items Based On,Type de critére de restriction
|
||||
Based On Value,critére de restriction
|
||||
Unit of Measure (UOM),Unité de mesure (UDM),
|
||||
Unit Of Measure (UOM),Unité de mesure (UDM),
|
||||
CRM Settings,Paramètres CRM
|
||||
Do Not Explode,Ne pas décomposer
|
||||
Quick Access, Accés rapides
|
||||
{} Available,{} Disponible.s
|
||||
{} Pending,{} En attente.s
|
||||
{} To Bill,{} à facturer
|
||||
{} To Receive,{} A recevoir
|
||||
{} Active,{} Actif.ve(s)
|
||||
{} Open,{} Ouvert.e(s)
|
||||
Incorrect Data Report,Rapport de données incohérentes
|
||||
Incorrect Serial No Valuation,Valorisation inccorecte par Num. Série / Lots
|
||||
Incorrect Balance Qty After Transaction,Equilibre des quantités aprés une transaction
|
||||
Interview Type,Type d'entretien
|
||||
Interview Round,Cycle d'entretien
|
||||
Interview,Entretien
|
||||
Interview Feedback,Retour d'entretien
|
||||
Journal Energy Point,Historique des points d'énergies
|
||||
Billing Address Details,Adresse de facturation (détails)
|
||||
Supplier Address Details,Adresse Fournisseur (détails)
|
||||
Retail,Commerce
|
||||
Users,Utilisateurs
|
||||
Permission Manager,Gestion des permissions
|
||||
Fetch Timesheet,Récuprer les temps saisis
|
||||
Get Supplier Group Details,Appliquer les informations depuis le Groupe de fournisseur
|
||||
Quality Inspection(s),Inspection(s) Qualité
|
||||
Set Advances and Allocate (FIFO),Affecter les encours au réglement
|
||||
Apply Putaway Rule,Appliquer la régle de routage d'entrepot
|
||||
Delete Transactions,Supprimer les transactions
|
||||
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
|
||||
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