Merge branch 'frappe:develop' into develop
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/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index c71ea36..2610c86 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -322,9 +322,9 @@
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,
)
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 1451189..8f0fe51 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1239,7 +1239,7 @@
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
),
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index a10a810..f7a57bb 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1444,7 +1444,7 @@
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
- supplier_condition = "and (release_date is null or release_date <= CURDATE())"
+ supplier_condition = "and (release_date is null or release_date <= CURRENT_DATE)"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
index d961076..39e9042 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
@@ -1,7 +1,6 @@
{
"actions": [],
"allow_rename": 1,
- "autoname": "format:PLE-{YY}-{MM}-{######}",
"creation": "2022-05-09 19:35:03.334361",
"doctype": "DocType",
"editable_grid": 1,
@@ -138,11 +137,10 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-05-19 18:04:44.609115",
+ "modified": "2022-05-30 19:04:55.532171",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",
- "naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py
index 3c45d20..ff9615d 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/payment_order.py
@@ -39,7 +39,7 @@
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 @@
""" 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},
)
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index e83dc0f..e8aee73 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -173,7 +173,7 @@
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,
)
diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json
index b9040e3..9916d16 100644
--- a/erpnext/accounts/module_onboarding/accounts/accounts.json
+++ b/erpnext/accounts/module_onboarding/accounts/accounts.json
@@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
"idx": 0,
"is_complete": 0,
- "modified": "2022-06-07 14:29:21.352132",
+ "modified": "2022-06-14 17:38:24.967834",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",
diff --git a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
index b6e9f5c..e323f6c 100644
--- a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
+++ b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
@@ -2,14 +2,14 @@
"action": "Create Entry",
"action_label": "Manage Sales Tax Templates",
"creation": "2020-05-13 19:29:43.844463",
- "description": "# Setting up Taxes\n\nERPNext lets you configure your taxes so that they are automatically applied in your buying and selling transactions. You can configure them globally or even on Items. ERPNext taxes are pre-configured for most regions.\n\n[Checkout pre-configured taxes](/app/sales-taxes-and-charges-template)\n",
+ "description": "# Setting up Taxes\n\nERPNext lets you configure your taxes so that they are automatically applied in your buying and selling transactions. You can configure them globally or even on Items. ERPNext taxes are pre-configured for most regions.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2022-06-07 14:27:15.906286",
+ "modified": "2022-06-14 17:37:56.694261",
"modified_by": "Administrator",
"name": "Setup Taxes",
"owner": "Administrator",
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index f4a44bd..e39f22b 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -211,7 +211,7 @@
else:
party_details.update(get_company_address(company))
- if doctype and doctype in ["Delivery Note", "Sales Invoice", "Sales Order"]:
+ if doctype and doctype in ["Delivery Note", "Sales Invoice", "Sales Order", "Quotation"]:
if party_details.company_address:
party_details.update(
get_fetch_values(doctype, "company_address", party_details.company_address)
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/report/inactive_sales_items/inactive_sales_items.py b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py
index 1a00399..230b18c 100644
--- a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py
+++ b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py
@@ -100,7 +100,7 @@
sales_data = frappe.db.sql(
"""
select s.territory, s.customer, si.item_group, si.item_code, si.qty, {date_field} as last_order_date,
- DATEDIFF(CURDATE(), {date_field}) as days_since_last_order
+ DATEDIFF(CURRENT_DATE, {date_field}) as days_since_last_order
from `tab{doctype}` s, `tab{doctype} Item` si
where s.name = si.parent and s.docstatus = 1
order by days_since_last_order """.format( # nosec
diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py
index 77c40ba..882cd69 100644
--- a/erpnext/accounts/test/test_utils.py
+++ b/erpnext/accounts/test/test_utils.py
@@ -62,8 +62,8 @@
stock_entry = {"item": item, "to_warehouse": "_Test Warehouse - _TC", "qty": 1, "rate": 10}
se1 = make_stock_entry(posting_date="2022-01-01", **stock_entry)
- se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
se3 = make_stock_entry(posting_date="2022-03-01", **stock_entry)
+ se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
for doc in (se1, se2, se3):
vouchers.append((doc.doctype, doc.name))
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 8711395..2d86dea 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -2,14 +2,18 @@
# License: GNU General Public License v3. See license.txt
+import itertools
from json import loads
-from typing import List, Tuple
+from typing import TYPE_CHECKING, List, Optional, Tuple
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
@@ -19,6 +23,9 @@
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.utils import get_stock_value_on
+if TYPE_CHECKING:
+ from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import RepostItemValuation
+
class FiscalYearError(frappe.ValidationError):
pass
@@ -28,6 +35,9 @@
pass
+GL_REPOSTING_CHUNK = 100
+
+
@frappe.whitelist()
def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
@@ -42,37 +52,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:
@@ -1122,7 +1127,11 @@
def repost_gle_for_stock_vouchers(
- stock_vouchers, posting_date, company=None, warehouse_account=None
+ stock_vouchers: List[Tuple[str, str]],
+ posting_date: str,
+ company: Optional[str] = None,
+ warehouse_account=None,
+ repost_doc: Optional["RepostItemValuation"] = None,
):
from erpnext.accounts.general_ledger import toggle_debit_credit_if_negative
@@ -1130,40 +1139,50 @@
if not stock_vouchers:
return
- def _delete_gl_entries(voucher_type, voucher_no):
- frappe.db.sql(
- """delete from `tabGL Entry`
- where voucher_type=%s and voucher_no=%s""",
- (voucher_type, voucher_no),
- )
-
- stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
-
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
+ stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
+ if repost_doc and repost_doc.gl_reposting_index:
+ # Restore progress
+ stock_vouchers = stock_vouchers[cint(repost_doc.gl_reposting_index) :]
+
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2
- gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
- for idx, (voucher_type, voucher_no) in enumerate(stock_vouchers):
- existing_gle = gle.get((voucher_type, voucher_no), [])
- voucher_obj = frappe.get_doc(voucher_type, voucher_no)
- # Some transactions post credit as negative debit, this is handled while posting GLE
- # but while comparing we need to make sure it's flipped so comparisons are accurate
- expected_gle = toggle_debit_credit_if_negative(voucher_obj.get_gl_entries(warehouse_account))
- if expected_gle:
- if not existing_gle or not compare_existing_and_expected_gle(
- existing_gle, expected_gle, precision
- ):
- _delete_gl_entries(voucher_type, voucher_no)
- voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
- else:
- _delete_gl_entries(voucher_type, voucher_no)
+ stock_vouchers_iterator = iter(stock_vouchers)
- if idx % 20 == 0:
- # Commit every 20 documents to avoid losing progress
- # and reducing memory usage
- frappe.db.commit()
+ while stock_vouchers_chunk := list(itertools.islice(stock_vouchers_iterator, GL_REPOSTING_CHUNK)):
+ gle = get_voucherwise_gl_entries(stock_vouchers_chunk, posting_date)
+
+ for voucher_type, voucher_no in stock_vouchers_chunk:
+ existing_gle = gle.get((voucher_type, voucher_no), [])
+ voucher_obj = frappe.get_doc(voucher_type, voucher_no)
+ # Some transactions post credit as negative debit, this is handled while posting GLE
+ # but while comparing we need to make sure it's flipped so comparisons are accurate
+ expected_gle = toggle_debit_credit_if_negative(voucher_obj.get_gl_entries(warehouse_account))
+ if expected_gle:
+ if not existing_gle or not compare_existing_and_expected_gle(
+ existing_gle, expected_gle, precision
+ ):
+ _delete_gl_entries(voucher_type, voucher_no)
+ voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
+ else:
+ _delete_gl_entries(voucher_type, voucher_no)
+ frappe.db.commit()
+
+ if repost_doc:
+ repost_doc.db_set(
+ "gl_reposting_index",
+ cint(repost_doc.gl_reposting_index) + GL_REPOSTING_CHUNK,
+ )
+
+
+def _delete_gl_entries(voucher_type, voucher_no):
+ frappe.db.sql(
+ """delete from `tabGL Entry`
+ where voucher_type=%s and voucher_no=%s""",
+ (voucher_type, voucher_no),
+ )
def sort_stock_vouchers_by_posting_date(
@@ -1177,6 +1196,9 @@
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
.groupby(sle.voucher_type, sle.voucher_no)
+ .orderby(sle.posting_date)
+ .orderby(sle.posting_time)
+ .orderby(sle.creation)
).run(as_dict=True)
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index a456c7f..61f6225 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -508,18 +508,6 @@
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
- "label": "DATEV Export",
- "link_count": 0,
- "link_to": "DATEV",
- "link_type": "Report",
- "onboard": 0,
- "only_for": "Germany",
- "type": "Link"
- },
- {
- "dependencies": "GL Entry",
- "hidden": 0,
- "is_query_report": 1,
"label": "UAE VAT 201",
"link_count": 0,
"link_to": "UAE VAT 201",
@@ -1024,16 +1012,16 @@
"type": "Link"
},
{
- "dependencies": "Cost Center",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Cost Center Allocation",
- "link_count": 0,
- "link_to": "Cost Center Allocation",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
+ "dependencies": "Cost Center",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Cost Center Allocation",
+ "link_count": 0,
+ "link_to": "Cost Center Allocation",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
{
"dependencies": "Cost Center",
"hidden": 0,
@@ -1235,13 +1223,14 @@
"type": "Link"
}
],
- "modified": "2022-01-13 17:25:09.835345",
+ "modified": "2022-06-10 15:49:42.990860",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
"owner": "Administrator",
"parent_page": "",
"public": 1,
+ "quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 2.0,
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 1a7f2dd..d732b75 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -330,7 +330,7 @@
else:
# update valid from
frappe.db.sql(
- """UPDATE `tabItem Tax` set valid_from = CURDATE()
+ """UPDATE `tabItem Tax` set valid_from = CURRENT_DATE
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template},
)
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index d39aec1..67affe7 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -285,7 +285,7 @@
"""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")},
)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index eeb5a7f..a725f67 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -33,7 +33,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
{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 @@
{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 @@
{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
@@ -691,7 +691,7 @@
def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
query = """select batch_id from `tabBatch`
where disabled = 0
- and (expiry_date >= CURDATE() or expiry_date IS NULL)
+ and (expiry_date >= CURRENT_DATE or expiry_date IS NULL)
and name like {txt}""".format(
txt=frappe.db.escape("%{0}%".format(txt))
)
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 3c0a10e..517e080 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -35,7 +35,8 @@
["Draft", None],
["Open", "eval:self.docstatus==1"],
["Lost", "eval:self.status=='Lost'"],
- ["Ordered", "has_sales_order"],
+ ["Partially Ordered", "is_partially_ordered"],
+ ["Ordered", "is_fully_ordered"],
["Cancelled", "eval:self.docstatus==2"],
],
"Sales Order": [
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index b590177..c70a4f6 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -8,7 +8,8 @@
from frappe import _
from frappe.email.inbox import link_communication_to_document
from frappe.model.mapper import get_mapped_doc
-from frappe.query_builder import DocType
+from frappe.query_builder import DocType, Interval
+from frappe.query_builder.functions import Now
from frappe.utils import cint, flt, get_fullname
from erpnext.crm.utils import add_link_in_communication, copy_comments
@@ -398,15 +399,17 @@
frappe.db.get_single_value("CRM Settings", "close_opportunity_after_days") or 15
)
- opportunities = frappe.db.sql(
- """ select name from tabOpportunity where status='Replied' and
- modified<DATE_SUB(CURDATE(), INTERVAL %s DAY) """,
- (auto_close_after_days),
- as_dict=True,
- )
+ table = frappe.qb.DocType("Opportunity")
+ opportunities = (
+ frappe.qb.from_(table)
+ .select(table.name)
+ .where(
+ (table.modified < (Now() - Interval(days=auto_close_after_days))) & (table.status == "Replied")
+ )
+ ).run(pluck=True)
for opportunity in opportunities:
- doc = frappe.get_doc("Opportunity", opportunity.get("name"))
+ doc = frappe.get_doc("Opportunity", opportunity)
doc.status = "Closed"
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index 44d68c9..81a0876 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -216,7 +216,7 @@
def make_employee_advance(employee_name, args=None):
doc = frappe.new_doc("Employee Advance")
doc.employee = employee_name
- doc.company = "_Test company"
+ doc.company = "_Test Company"
doc.purpose = "For site visit"
doc.currency = erpnext.get_company_currency("_Test company")
doc.exchange_rate = 1
diff --git a/erpnext/manufacturing/doctype/bom_update_batch/bom_update_batch.json b/erpnext/manufacturing/doctype/bom_update_batch/bom_update_batch.json
index 83b54d3..b867d2a 100644
--- a/erpnext/manufacturing/doctype/bom_update_batch/bom_update_batch.json
+++ b/erpnext/manufacturing/doctype/bom_update_batch/bom_update_batch.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "autoincrement",
+ "autoname": "hash",
"creation": "2022-05-31 17:34:39.825537",
"doctype": "DocType",
"engine": "InnoDB",
@@ -46,10 +46,9 @@
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Update Batch",
- "naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 0199a5c..ed45106 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -626,14 +626,15 @@
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
- if self.for_quantity <= self.transferred_qty:
- self.status = "Material Transferred"
+ if self.docstatus < 2:
+ if self.for_quantity <= self.transferred_qty:
+ self.status = "Material Transferred"
- if self.time_logs:
- self.status = "Work In Progress"
+ if self.time_logs:
+ self.status = "Work In Progress"
- if self.docstatus == 1 and (self.for_quantity <= self.total_completed_qty or not self.items):
- self.status = "Completed"
+ if self.docstatus == 1 and (self.for_quantity <= self.total_completed_qty or not self.items):
+ self.status = "Completed"
if update_status:
self.db_set("status", self.status)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_list.js b/erpnext/manufacturing/doctype/job_card/job_card_list.js
index 7f60bdc..5d883bf 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card_list.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card_list.js
@@ -1,16 +1,17 @@
frappe.listview_settings['Job Card'] = {
has_indicator_for_draft: true,
+
get_indicator: function(doc) {
- if (doc.status === "Work In Progress") {
- return [__("Work In Progress"), "orange", "status,=,Work In Progress"];
- } else if (doc.status === "Completed") {
- return [__("Completed"), "green", "status,=,Completed"];
- } else if (doc.docstatus == 2) {
- return [__("Cancelled"), "red", "status,=,Cancelled"];
- } else if (doc.status === "Material Transferred") {
- return [__('Material Transferred'), "blue", "status,=,Material Transferred"];
- } else {
- return [__("Open"), "red", "status,=,Open"];
- }
+ const status_colors = {
+ "Work In Progress": "orange",
+ "Completed": "green",
+ "Cancelled": "red",
+ "Material Transferred": "blue",
+ "Open": "red",
+ };
+ const status = doc.status || "Open";
+ const color = status_colors[status] || "blue";
+
+ return [__(status), color, `status,=,${status}`];
}
};
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index 7f3c7fe..ac71141 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -344,6 +344,30 @@
cost_after_cancel = self.work_order.total_operating_cost
self.assertEqual(cost_after_cancel, original_cost)
+ def test_job_card_statuses(self):
+ def assertStatus(status):
+ jc.set_status()
+ self.assertEqual(jc.status, status)
+
+ jc = frappe.new_doc("Job Card")
+ jc.for_quantity = 2
+ jc.transferred_qty = 1
+ jc.total_completed_qty = 0
+ assertStatus("Open")
+
+ jc.transferred_qty = jc.for_quantity
+ assertStatus("Material Transferred")
+
+ jc.append("time_logs", {})
+ assertStatus("Work In Progress")
+
+ jc.docstatus = 1
+ jc.total_completed_qty = jc.for_quantity
+ assertStatus("Completed")
+
+ jc.docstatus = 2
+ assertStatus("Cancelled")
+
def create_bom_with_multiple_operations():
"Create a BOM with multiple operations and Material Transfer against Job Card"
diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
index 3fe2198..da28343 100644
--- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
+++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
@@ -102,7 +102,7 @@
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},
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index 9829a96..549f5af 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 17:11:37.032604",
"docstatus": 0,
"doctype": "Workspace",
@@ -402,7 +402,7 @@
"type": "Link"
}
],
- "modified": "2022-05-31 22:08:19.408223",
+ "modified": "2022-06-15 15:18:57.062935",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
@@ -415,39 +415,35 @@
"sequence_id": 17.0,
"shortcuts": [
{
- "color": "Green",
- "format": "{} Active",
- "label": "Item",
- "link_to": "Item",
- "restrict_to_domain": "Manufacturing",
- "stats_filter": "{\n \"disabled\": 0\n}",
- "type": "DocType"
- },
- {
- "color": "Green",
- "format": "{} Active",
+ "color": "Grey",
+ "doc_view": "List",
"label": "BOM",
"link_to": "BOM",
- "restrict_to_domain": "Manufacturing",
- "stats_filter": "{\n \"is_active\": 1\n}",
+ "stats_filter": "{\"is_active\":[\"=\",1]}",
"type": "DocType"
},
{
- "color": "Yellow",
- "format": "{} Open",
- "label": "Work Order",
- "link_to": "Work Order",
- "restrict_to_domain": "Manufacturing",
- "stats_filter": "{ \n \"status\": [\"in\", \n [\"Draft\", \"Not Started\", \"In Process\"]\n ]\n}",
- "type": "DocType"
- },
- {
- "color": "Yellow",
- "format": "{} Open",
+ "color": "Grey",
+ "doc_view": "List",
"label": "Production Plan",
"link_to": "Production Plan",
- "restrict_to_domain": "Manufacturing",
- "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}",
+ "stats_filter": "{\"status\":[\"not in\",[\"Closed\",\"Cancelled\",\"Completed\"]]}",
+ "type": "DocType"
+ },
+ {
+ "color": "Grey",
+ "doc_view": "List",
+ "label": "Work Order",
+ "link_to": "Work Order",
+ "stats_filter": "{\"status\":[\"not in\",[\"Closed\",\"Cancelled\",\"Completed\"]]}",
+ "type": "DocType"
+ },
+ {
+ "color": "Grey",
+ "doc_view": "List",
+ "label": "Job Card",
+ "link_to": "Job Card",
+ "stats_filter": "{\"status\":[\"not in\",[\"Cancelled\",\"Completed\",null]]}",
"type": "DocType"
},
{
@@ -456,12 +452,6 @@
"type": "Report"
},
{
- "label": "Work Order Summary",
- "link_to": "Work Order Summary",
- "restrict_to_domain": "Manufacturing",
- "type": "Report"
- },
- {
"label": "BOM Stock Report",
"link_to": "BOM Stock Report",
"type": "Report"
@@ -470,12 +460,6 @@
"label": "Production Planning Report",
"link_to": "Production Planning Report",
"type": "Report"
- },
- {
- "label": "Dashboard",
- "link_to": "Manufacturing",
- "restrict_to_domain": "Manufacturing",
- "type": "Dashboard"
}
],
"title": "Manufacturing"
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 5a98463..318875d 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -339,7 +339,7 @@
erpnext.patches.v14_0.delete_healthcare_doctypes
erpnext.patches.v14_0.delete_hub_doctypes
erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
-erpnext.patches.v14_0.delete_agriculture_doctypes
+erpnext.patches.v14_0.delete_agriculture_doctypes # 15-06-2022
erpnext.patches.v14_0.delete_education_doctypes
erpnext.patches.v14_0.delete_datev_doctypes
erpnext.patches.v14_0.rearrange_company_fields
@@ -374,3 +374,4 @@
execute:frappe.delete_doc("DocType", "Naming Series")
erpnext.patches.v13_0.set_payroll_entry_status
erpnext.patches.v13_0.job_card_status_on_hold
+erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v14_0/delete_agriculture_doctypes.py b/erpnext/patches/v14_0/delete_agriculture_doctypes.py
index e0b12a2..8ec0c33 100644
--- a/erpnext/patches/v14_0/delete_agriculture_doctypes.py
+++ b/erpnext/patches/v14_0/delete_agriculture_doctypes.py
@@ -2,6 +2,9 @@
def execute():
+ if "agriculture" in frappe.get_installed_apps():
+ return
+
frappe.delete_doc("Module Def", "Agriculture", ignore_missing=True, force=True)
frappe.delete_doc("Workspace", "Agriculture", ignore_missing=True, force=True)
@@ -19,3 +22,5 @@
doctypes = frappe.get_all("DocType", {"module": "agriculture", "custom": 0}, pluck="name")
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
+
+ frappe.delete_doc("Module Def", "Agriculture", ignore_missing=True, force=True)
diff --git a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
index c2267aa..1e0d20d 100644
--- a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
+++ b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
@@ -1,11 +1,13 @@
import frappe
from frappe import qb
+from frappe.query_builder import Case
+from frappe.query_builder.custom import ConstantColumn
+from frappe.query_builder.functions import IfNull
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_dimensions,
make_dimension_in_accounting_doctypes,
)
-from erpnext.accounts.utils import create_payment_ledger_entry
def create_accounting_dimension_fields():
@@ -15,24 +17,119 @@
make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
-def execute():
- # create accounting dimension fields in Payment Ledger
- create_accounting_dimension_fields()
+def generate_name_for_payment_ledger_entries(gl_entries):
+ for index, entry in enumerate(gl_entries, 1):
+ entry.name = index
- gl = qb.DocType("GL Entry")
- accounts = frappe.db.get_list(
- "Account", "name", filters={"account_type": ["in", ["Receivable", "Payable"]]}, as_list=True
- )
- gl_entries = []
- if accounts:
- # get all gl entries on receivable/payable accounts
+
+def get_columns():
+ columns = [
+ "name",
+ "creation",
+ "modified",
+ "modified_by",
+ "owner",
+ "docstatus",
+ "posting_date",
+ "account_type",
+ "account",
+ "party_type",
+ "party",
+ "voucher_type",
+ "voucher_no",
+ "against_voucher_type",
+ "against_voucher_no",
+ "amount",
+ "amount_in_account_currency",
+ "account_currency",
+ "company",
+ "cost_center",
+ "due_date",
+ "finance_book",
+ ]
+
+ dimensions_and_defaults = get_dimensions()
+ if dimensions_and_defaults:
+ for dimension in dimensions_and_defaults[0]:
+ columns.append(dimension.fieldname)
+
+ return columns
+
+
+def build_insert_query():
+ ple = qb.DocType("Payment Ledger Entry")
+ columns = get_columns()
+ insert_query = qb.into(ple)
+
+ # build 'insert' columns in query
+ insert_query = insert_query.columns(tuple(columns))
+
+ return insert_query
+
+
+def insert_chunk_into_payment_ledger(insert_query, gl_entries):
+ if gl_entries:
+ columns = get_columns()
+
+ # build tuple of data with same column order
+ for entry in gl_entries:
+ data = ()
+ for column in columns:
+ data += (entry[column],)
+ insert_query = insert_query.insert(data)
+ insert_query.run()
+
+
+def execute():
+ if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
+ # create accounting dimension fields in Payment Ledger
+ create_accounting_dimension_fields()
+
+ gl = qb.DocType("GL Entry")
+ account = qb.DocType("Account")
+
gl_entries = (
qb.from_(gl)
- .select("*")
- .where(gl.account.isin(accounts))
+ .inner_join(account)
+ .on((gl.account == account.name) & (account.account_type.isin(["Receivable", "Payable"])))
+ .select(
+ gl.star,
+ ConstantColumn(1).as_("docstatus"),
+ account.account_type.as_("account_type"),
+ IfNull(gl.against_voucher_type, gl.voucher_type).as_("against_voucher_type"),
+ IfNull(gl.against_voucher, gl.voucher_no).as_("against_voucher_no"),
+ # convert debit/credit to amount
+ Case()
+ .when(account.account_type == "Receivable", gl.debit - gl.credit)
+ .else_(gl.credit - gl.debit)
+ .as_("amount"),
+ # convert debit/credit in account currency to amount in account currency
+ Case()
+ .when(
+ account.account_type == "Receivable",
+ gl.debit_in_account_currency - gl.credit_in_account_currency,
+ )
+ .else_(gl.credit_in_account_currency - gl.debit_in_account_currency)
+ .as_("amount_in_account_currency"),
+ )
.where(gl.is_cancelled == 0)
+ .orderby(gl.creation)
.run(as_dict=True)
)
- if gl_entries:
- # create payment ledger entries for the accounts receivable/payable
- create_payment_ledger_entry(gl_entries, 0)
+
+ # primary key(name) for payment ledger records
+ generate_name_for_payment_ledger_entries(gl_entries)
+
+ # split data into chunks
+ chunk_size = 1000
+ try:
+ for i in range(0, len(gl_entries), chunk_size):
+ insert_query = build_insert_query()
+ insert_chunk_into_payment_ledger(insert_query, gl_entries[i : i + chunk_size])
+ frappe.db.commit()
+ except Exception as err:
+ frappe.db.rollback()
+ ple = qb.DocType("Payment Ledger Entry")
+ qb.from_(ple).delete().where(ple.docstatus >= 0).run()
+ frappe.db.commit()
+ raise err
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 620fcad..1524fb7 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -970,7 +970,7 @@
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 @@
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),
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 8a8e1d1..c613fe6 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -391,7 +391,7 @@
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),
diff --git a/erpnext/projects/doctype/project_update/project_update.py b/erpnext/projects/doctype/project_update/project_update.py
index 5a29fb6..175f787 100644
--- a/erpnext/projects/doctype/project_update/project_update.py
+++ b/erpnext/projects/doctype/project_update/project_update.py
@@ -28,7 +28,7 @@
for drafts in draft:
number_of_drafts = drafts[0]
update = frappe.db.sql(
- """SELECT name,date,time,progress,progress_details FROM `tabProject Update` WHERE `tabProject Update`.project = %s AND date = DATE_ADD(CURDATE(), INTERVAL -1 DAY);""",
+ """SELECT name,date,time,progress,progress_details FROM `tabProject Update` WHERE `tabProject Update`.project = %s AND date = DATE_ADD(CURRENT_DATE, INTERVAL -1 DAY);""",
project_name,
)
email_sending(project_name, frequency, date_start, date_end, progress, number_of_drafts, update)
@@ -39,7 +39,7 @@
):
holiday = frappe.db.sql(
- """SELECT holiday_date FROM `tabHoliday` where holiday_date = CURDATE();"""
+ """SELECT holiday_date FROM `tabHoliday` where holiday_date = CURRENT_DATE;"""
)
msg = (
"<p>Project Name: "
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 4575fb5..0e409fc 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -288,7 +288,7 @@
%(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
),
{
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 2ef966b..88d5bee 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -328,7 +328,7 @@
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 @@
tsd.project IN %(projects)s
)
ORDER BY `end_date` ASC
- LIMIT {0}, {1}
+ LIMIT {1} offset {0}
""".format(
limit_start, limit_page_length
),
diff --git a/erpnext/projects/utils.py b/erpnext/projects/utils.py
index 000ea66..3cc4da4 100644
--- a/erpnext/projects/utils.py
+++ b/erpnext/projects/utils.py
@@ -25,7 +25,7 @@
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),
)
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index ee48ccb..0262469 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -287,7 +287,7 @@
return party_details
if (
- doctype in ("Sales Invoice", "Delivery Note", "Sales Order")
+ doctype in ("Sales Invoice", "Delivery Note", "Sales Order", "Quotation")
and party_details.company_gstin
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]
) or (
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/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py
index 575b956..ac83c0f 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.py
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.py
@@ -78,7 +78,7 @@
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),
)
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 34e9a52..70ae085 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -20,6 +20,20 @@
frm.set_df_property('packed_items', 'cannot_add_rows', true);
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
+
+ frm.set_query('company_address', function(doc) {
+ if(!doc.company) {
+ frappe.throw(__('Please set Company'));
+ }
+
+ return {
+ query: 'frappe.contacts.doctype.address.address.address_query',
+ filters: {
+ link_doctype: 'Company',
+ link_name: doc.company
+ }
+ };
+ });
},
refresh: function(frm) {
@@ -70,7 +84,7 @@
}
}
- if(doc.docstatus == 1 && doc.status!=='Lost') {
+ if(doc.docstatus == 1 && !(['Lost', 'Ordered']).includes(doc.status)) {
if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
cur_frm.add_custom_button(__('Sales Order'),
cur_frm.cscript['Make Sales Order'], __('Create'));
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 75443ab..bb2f95d 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -296,7 +296,7 @@
"read_only": 1
},
{
- "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
+ "depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
"fieldname": "col_break98",
"fieldtype": "Column Break",
"width": "50%"
@@ -316,7 +316,7 @@
"read_only": 1
},
{
- "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
+ "depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 1,
@@ -897,7 +897,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Draft\nOpen\nReplied\nOrdered\nLost\nCancelled\nExpired",
+ "options": "Draft\nOpen\nReplied\nPartially Ordered\nOrdered\nLost\nCancelled\nExpired",
"print_hide": 1,
"read_only": 1,
"reqd": 1
@@ -986,7 +986,7 @@
"idx": 82,
"is_submittable": 1,
"links": [],
- "modified": "2022-04-07 11:01:31.157084",
+ "modified": "2022-06-11 20:35:32.635804",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
@@ -1084,4 +1084,4 @@
"states": [],
"timeline_field": "party_name",
"title_field": "title"
-}
\ No newline at end of file
+}
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 548813d..d5fd946 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -70,8 +70,32 @@
title=_("Unpublished Item"),
)
- def has_sales_order(self):
- return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})
+ def get_ordered_status(self):
+ ordered_items = frappe._dict(
+ frappe.db.get_all(
+ "Sales Order Item",
+ {"prevdoc_docname": self.name, "docstatus": 1},
+ ["item_code", "sum(qty)"],
+ group_by="item_code",
+ as_list=1,
+ )
+ )
+
+ status = "Open"
+ if ordered_items:
+ status = "Ordered"
+
+ for item in self.get("items"):
+ if item.qty > ordered_items.get(item.item_code, 0.0):
+ status = "Partially Ordered"
+
+ return status
+
+ def is_fully_ordered(self):
+ return self.get_ordered_status() == "Ordered"
+
+ def is_partially_ordered(self):
+ return self.get_ordered_status() == "Partially Ordered"
def update_lead(self):
if self.quotation_to == "Lead" and self.party_name:
diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js
index 4c8f9c4..32fce1f 100644
--- a/erpnext/selling/doctype/quotation/quotation_list.js
+++ b/erpnext/selling/doctype/quotation/quotation_list.js
@@ -25,6 +25,8 @@
get_indicator: function(doc) {
if(doc.status==="Open") {
return [__("Open"), "orange", "status,=,Open"];
+ } else if (doc.status==="Partially Ordered") {
+ return [__("Partially Ordered"), "yellow", "status,=,Partially Ordered"];
} else if(doc.status==="Ordered") {
return [__("Ordered"), "green", "status,=,Ordered"];
} else if(doc.status==="Lost") {
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 7522e92..8c03cb5 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -25,6 +25,7 @@
from erpnext.selling.doctype.customer.customer import check_credit_limit
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.item.item import get_item_defaults
+from erpnext.stock.get_item_details import get_default_bom
from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
@@ -423,8 +424,9 @@
for table in [self.items, self.packed_items]:
for i in table:
- bom = get_default_bom_item(i.item_code)
+ bom = get_default_bom(i.item_code)
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
+
if not for_raw_material_request:
total_work_order_qty = flt(
frappe.db.sql(
@@ -438,32 +440,19 @@
pending_qty = stock_qty
if pending_qty and i.item_code not in product_bundle_parents:
- if bom:
- items.append(
- dict(
- name=i.name,
- item_code=i.item_code,
- description=i.description,
- bom=bom,
- warehouse=i.warehouse,
- pending_qty=pending_qty,
- required_qty=pending_qty if for_raw_material_request else 0,
- sales_order_item=i.name,
- )
+ items.append(
+ dict(
+ name=i.name,
+ item_code=i.item_code,
+ description=i.description,
+ bom=bom or "",
+ warehouse=i.warehouse,
+ pending_qty=pending_qty,
+ required_qty=pending_qty if for_raw_material_request else 0,
+ sales_order_item=i.name,
)
- else:
- items.append(
- dict(
- name=i.name,
- item_code=i.item_code,
- description=i.description,
- bom="",
- warehouse=i.warehouse,
- pending_qty=pending_qty,
- required_qty=pending_qty if for_raw_material_request else 0,
- sales_order_item=i.name,
- )
- )
+ )
+
return items
def on_recurring(self, reference_doc, auto_repeat_doc):
@@ -1167,13 +1156,6 @@
so.update_status(status)
-def get_default_bom_item(item_code):
- bom = frappe.get_all("BOM", dict(item=item_code, is_active=True), order_by="is_default desc")
- bom = bom[0].name if bom else None
-
- return bom
-
-
@frappe.whitelist()
def make_raw_material_request(items, company, sales_order, project=None):
if not frappe.has_permission("Sales Order", "write"):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 96308f0..45868fb 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -644,7 +644,7 @@
else:
# update valid from
frappe.db.sql(
- """UPDATE `tabItem Tax` set valid_from = CURDATE()
+ """UPDATE `tabItem Tax` set valid_from = CURRENT_DATE
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template},
)
@@ -1380,6 +1380,59 @@
except Exception:
self.fail("Can not cancel sales order with linked cancelled payment entry")
+ def test_work_order_pop_up_from_sales_order(self):
+ "Test `get_work_order_items` in Sales Order picks the right BOM for items to manufacture."
+
+ from erpnext.controllers.item_variant import create_variant
+ from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+ make_item( # template item
+ "Test-WO-Tshirt",
+ {
+ "has_variant": 1,
+ "variant_based_on": "Item Attribute",
+ "attributes": [{"attribute": "Test Colour"}],
+ },
+ )
+ make_item("Test-RM-Cotton") # RM for BOM
+
+ for colour in (
+ "Red",
+ "Green",
+ ):
+ variant = create_variant("Test-WO-Tshirt", {"Test Colour": colour})
+ variant.save()
+
+ template_bom = make_bom(item="Test-WO-Tshirt", rate=100, raw_materials=["Test-RM-Cotton"])
+ red_var_bom = make_bom(item="Test-WO-Tshirt-R", rate=100, raw_materials=["Test-RM-Cotton"])
+
+ so = make_sales_order(
+ **{
+ "item_list": [
+ {
+ "item_code": "Test-WO-Tshirt-R",
+ "qty": 1,
+ "rate": 1000,
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ {
+ "item_code": "Test-WO-Tshirt-G",
+ "qty": 1,
+ "rate": 1000,
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ ]
+ }
+ )
+ wo_items = so.get_work_order_items()
+
+ self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
+ self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
+
+ # Must pick Template Item BOM for Test-WO-Tshirt-G as it has no BOM
+ self.assertEqual(wo_items[1].get("item_code"), "Test-WO-Tshirt-G")
+ self.assertEqual(wo_items[1].get("bom"), template_bom.name)
+
def test_request_for_raw_materials(self):
item = make_item(
"_Test Finished Item",
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 99afe81..13d5069 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -107,7 +107,7 @@
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 @@
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},
diff --git a/erpnext/selling/report/inactive_customers/inactive_customers.py b/erpnext/selling/report/inactive_customers/inactive_customers.py
index 1b337fc..a166085 100644
--- a/erpnext/selling/report/inactive_customers/inactive_customers.py
+++ b/erpnext/selling/report/inactive_customers/inactive_customers.py
@@ -31,13 +31,13 @@
def get_sales_details(doctype):
cond = """sum(so.base_net_total) as 'total_order_considered',
max(so.posting_date) as 'last_order_date',
- DATEDIFF(CURDATE(), max(so.posting_date)) as 'days_since_last_order' """
+ DATEDIFF(CURRENT_DATE, max(so.posting_date)) as 'days_since_last_order' """
if doctype == "Sales Order":
cond = """sum(if(so.status = "Stopped",
so.base_net_total * so.per_delivered/100,
so.base_net_total)) as 'total_order_considered',
max(so.transaction_date) as 'last_order_date',
- DATEDIFF(CURDATE(), max(so.transaction_date)) as 'days_since_last_order'"""
+ DATEDIFF(CURRENT_DATE, max(so.transaction_date)) as 'days_since_last_order'"""
return frappe.db.sql(
"""select
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index cc61594..720aa41 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -64,7 +64,7 @@
soi.delivery_date as delivery_date,
so.name as sales_order,
so.status, so.customer, soi.item_code,
- DATEDIFF(CURDATE(), soi.delivery_date) as delay_days,
+ DATEDIFF(CURRENT_DATE, soi.delivery_date) as delay_days,
IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
soi.qty, soi.delivered_qty,
(soi.qty - soi.delivered_qty) AS pending_qty,
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index cdfea77..42ba6ce 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -854,7 +854,7 @@
sql_po = """select {fields} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
- where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and curdate() > `tabPurchase Order Item`.schedule_date
+ where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and CURRENT_DATE > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.parent DESC,
`tabPurchase Order Item`.schedule_date DESC""".format(
fields=fields_po
@@ -862,7 +862,7 @@
sql_poi = """select {fields} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
- where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and curdate() > `tabPurchase Order Item`.schedule_date
+ where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and CURRENT_DATE > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.idx""".format(
fields=fields_poi
)
diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py
index d07ab08..cf7cba8 100644
--- a/erpnext/setup/doctype/party_type/party_type.py
+++ b/erpnext/setup/doctype/party_type/party_type.py
@@ -21,7 +21,7 @@
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},
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 559883f..52854a0 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -335,7 +335,7 @@
on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
and `tabStock Ledger Entry`.is_cancelled = 0
- and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
+ and (`tabBatch`.expiry_date >= CURRENT_DATE or `tabBatch`.expiry_date IS NULL) {0}
group by batch_id
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
""".format(
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 2f6d4fb..76cb31d 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -14,7 +14,6 @@
"details",
"naming_series",
"item_code",
- "variant_of",
"item_name",
"item_group",
"stock_uom",
@@ -22,6 +21,7 @@
"disabled",
"allow_alternative_item",
"is_stock_item",
+ "has_variants",
"include_item_in_manufacturing",
"opening_stock",
"valuation_rate",
@@ -66,7 +66,7 @@
"has_serial_no",
"serial_no_series",
"variants_section",
- "has_variants",
+ "variant_of",
"variant_based_on",
"attributes",
"accounting",
@@ -112,8 +112,8 @@
"quality_inspection_template",
"inspection_required_before_delivery",
"manufacturing",
- "default_bom",
"is_sub_contracted_item",
+ "default_bom",
"column_break_74",
"customer_code",
"default_item_manufacturer",
@@ -479,7 +479,7 @@
"collapsible_depends_on": "attributes",
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "variants_section",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Variants"
},
{
@@ -504,7 +504,8 @@
"fieldname": "attributes",
"fieldtype": "Table",
"hidden": 1,
- "label": "Attributes",
+ "label": "Variant Attributes",
+ "mandatory_depends_on": "has_variants",
"options": "Item Variant Attribute"
},
{
@@ -909,7 +910,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-06-08 11:35:20.094546",
+ "modified": "2022-06-15 09:02:06.177691",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py
index 0f93bb9..fb1a28d 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -77,7 +77,7 @@
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
),
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 1af9953..1ba8011 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -24,7 +24,7 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
get_multiple_items=True,
get_taxes_and_charges=True,
)
@@ -195,7 +195,7 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
get_multiple_items=True,
get_taxes_and_charges=True,
do_not_submit=True,
@@ -280,7 +280,7 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
do_not_save=True,
)
pr.items[0].cost_center = "Main - TCP1"
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index e9ccf5f..e5b9de8 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -203,7 +203,7 @@
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),
)
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 7fbfa62..be4f274 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -276,7 +276,7 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
get_multiple_items=True,
get_taxes_and_charges=True,
)
@@ -486,13 +486,13 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-2,
@@ -573,13 +573,13 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-5,
@@ -615,7 +615,7 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
qty=2,
rejected_qty=2,
rejected_warehouse=rejected_warehouse,
@@ -624,7 +624,7 @@
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-2,
@@ -951,7 +951,7 @@
cost_center=cost_center,
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
@@ -975,7 +975,7 @@
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 331d3e8..13abfad 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -232,7 +232,7 @@
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 @@
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,
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index 156f77f..e093933 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -25,7 +25,8 @@
"items_to_be_repost",
"affected_transactions",
"distinct_item_and_warehouse",
- "current_index"
+ "current_index",
+ "gl_reposting_index"
],
"fields": [
{
@@ -181,12 +182,20 @@
"label": "Affected Transactions",
"no_copy": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "gl_reposting_index",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "GL reposting index",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-04-18 14:08:08.821602",
+ "modified": "2022-06-13 12:20:22.182322",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 328afc8..ea24b47 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -192,6 +192,7 @@
directly_dependent_transactions + list(repost_affected_transaction),
doc.posting_date,
doc.company,
+ repost_doc=doc,
)
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
index 3184f69..3c74619 100644
--- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -2,10 +2,14 @@
# See license.txt
+from unittest.mock import MagicMock, call
+
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
+from frappe.utils.data import today
+from erpnext.accounts.utils import repost_gle_for_stock_vouchers
from erpnext.controllers.stock_controller import create_item_wise_repost_entries
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -193,3 +197,31 @@
[["a", "b"], ["c", "d"]],
sorted(frappe.parse_json(frappe.as_json(set([("a", "b"), ("c", "d")])))),
)
+
+ def test_gl_repost_progress(self):
+ from erpnext.accounts import utils
+
+ # lower numbers to simplify test
+ orig_chunk_size = utils.GL_REPOSTING_CHUNK
+ utils.GL_REPOSTING_CHUNK = 1
+ self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size)
+
+ doc = frappe.new_doc("Repost Item Valuation")
+ doc.db_set = MagicMock()
+
+ vouchers = []
+ company = "_Test Company with perpetual inventory"
+ posting_date = today()
+
+ for _ in range(3):
+ se = make_stock_entry(company=company, qty=1, rate=2, target="Stores - TCP1")
+ vouchers.append((se.doctype, se.name))
+
+ repost_gle_for_stock_vouchers(stock_vouchers=vouchers, posting_date=posting_date, repost_doc=doc)
+ self.assertIn(call("gl_reposting_index", 1), doc.db_set.mock_calls)
+ doc.db_set.reset_mock()
+
+ doc.gl_reposting_index = 1
+ repost_gle_for_stock_vouchers(stock_vouchers=vouchers, posting_date=posting_date, repost_doc=doc)
+
+ self.assertNotIn(call("gl_reposting_index", 1), doc.db_set.mock_calls)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f1df54d..a9176a9 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1980,23 +1980,30 @@
):
# 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):
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index c8d9f54..3776a27 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -1352,12 +1352,22 @@
@frappe.whitelist()
def get_default_bom(item_code=None):
- if item_code:
- bom = frappe.db.get_value(
- "BOM", {"docstatus": 1, "is_default": 1, "is_active": 1, "item": item_code}
+ def _get_bom(item):
+ bom = frappe.get_all(
+ "BOM", dict(item=item, is_active=True, is_default=True, docstatus=1), limit=1
)
- if bom:
- return bom
+ return bom[0].name if bom else None
+
+ if not item_code:
+ return
+
+ bom_name = _get_bom(item_code)
+
+ template_item = frappe.db.get_value("Item", item_code, "variant_of")
+ if not bom_name and template_item:
+ bom_name = _get_bom(template_item)
+
+ return bom_name
@frappe.whitelist()
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 08a06b1..7f3e0cf 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -11,6 +11,8 @@
from frappe.email.inbox import link_communication_to_document
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Now
from frappe.utils import date_diff, get_datetime, now_datetime, time_diff_in_seconds
from frappe.utils.user import is_website_user
@@ -190,15 +192,17 @@
frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7
)
- issues = frappe.db.sql(
- """ select name from tabIssue where status='Replied' and
- modified<DATE_SUB(CURDATE(), INTERVAL %s DAY) """,
- (auto_close_after_days),
- as_dict=True,
- )
+ table = frappe.qb.DocType("Issue")
+ issues = (
+ frappe.qb.from_(table)
+ .select(table.name)
+ .where(
+ (table.modified < (Now() - Interval(days=auto_close_after_days))) & (table.status == "Replied")
+ )
+ ).run(pluck=True)
for issue in issues:
- doc = frappe.get_doc("Issue", issue.get("name"))
+ doc = frappe.get_doc("Issue", issue)
doc.status = "Closed"
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index 3ed056f..0768cc3 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -56,7 +56,10 @@
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
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/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py
index bf12181..93d1c8e 100644
--- a/erpnext/tests/test_subcontracting.py
+++ b/erpnext/tests/test_subcontracting.py
@@ -879,6 +879,55 @@
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 = {}
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 22e3c35..ffc46d2 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -1352,11 +1352,11 @@
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 @@
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 @@
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 @@
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 @@
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
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)