Merge pull request #38580 from ruthra-kumar/finance_book_filter_bug_in_general_ledger
fix: sql error while filtering on finance book in GL
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 4f7eeba..e9cbb33 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -214,7 +214,7 @@
# round off balance based on currency precision
# and consider debit-credit difference allowance
currency_precision = get_currency_precision()
- rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
+ rounding_loss_allowance = float(rounding_loss_allowance)
for acc in account_details:
acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 9684a0d..266154d 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -8,7 +8,7 @@
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
- frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"];
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
},
refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index e4f1645..dd3e9d0 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -170,6 +170,8 @@
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
+ "Unreconcile Payment",
+ "Unreconcile Payment Entries",
)
self.make_gl_entries(1)
self.update_advance_paid()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 82552bf..1282ab6 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -9,6 +9,8 @@
from frappe import ValidationError, _, qb, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils.data import comma_and, fmt_money
+from pypika import Case
+from pypika.functions import Coalesce, Sum
import erpnext
from erpnext.accounts.doctype.bank_account.bank_account import (
@@ -104,9 +106,17 @@
self.set_status()
def set_liability_account(self):
- if not self.book_advance_payments_in_separate_party_account:
+ # Auto setting liability account should only be done during 'draft' status
+ if self.docstatus > 0:
return
+ if not frappe.db.get_value(
+ "Company", self.company, "book_advance_payments_in_separate_party_account"
+ ):
+ return
+
+ # Important to set this flag for the gl building logic to work properly
+ self.book_advance_payments_in_separate_party_account = True
account_type = frappe.get_value(
"Account", {"name": self.party_account, "company": self.company}, "account_type"
)
@@ -116,11 +126,13 @@
):
return
- if self.unallocated_amount == 0:
- for d in self.references:
- if d.reference_doctype in ["Sales Order", "Purchase Order"]:
- break
- else:
+ if self.references:
+ allowed_types = frozenset(["Sales Order", "Purchase Order"])
+ reference_types = set([x.reference_doctype for x in self.references])
+
+ # If there are referencers other than `allowed_types`, treat this as a normal payment entry
+ if reference_types - allowed_types:
+ self.book_advance_payments_in_separate_party_account = False
return
liability_account = get_party_account(
@@ -1980,18 +1992,24 @@
def get_outstanding_on_journal_entry(name):
- res = frappe.db.sql(
- "SELECT "
- 'CASE WHEN party_type IN ("Customer") '
- "THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) "
- "ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) "
- "END as outstanding_amount "
- "FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) "
- "AND party_type IS NOT NULL "
- 'AND party_type != ""',
- (name, name),
- as_dict=1,
- )
+ gl = frappe.qb.DocType("GL Entry")
+ res = (
+ frappe.qb.from_(gl)
+ .select(
+ Case()
+ .when(
+ gl.party_type == "Customer",
+ Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
+ )
+ .else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
+ .as_("outstanding_amount")
+ )
+ .where(
+ (Coalesce(gl.party_type, "") != "")
+ & (gl.is_cancelled == 0)
+ & ((gl.voucher_no == name) | (gl.against_voucher == name))
+ )
+ ).run(as_dict=True)
outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 4b0df12..cebd61a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -35,7 +35,7 @@
super.onload();
// Ignore linked advances
- this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"];
+ this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
if(!this.frm.doc.__islocal) {
// show credit_to in print format
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index f9c9fb5..7156fa2 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1449,6 +1449,8 @@
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
+ "Unreconcile Payment",
+ "Unreconcile Payment Entries",
"Payment Ledger Entry",
"Tax Withheld Vouchers",
"Serial and Batch Bundle",
@@ -1859,6 +1861,4 @@
target_doc,
)
- doc.set_onload("ignore_price_list", True)
-
return doc
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 0ebf335..96a557b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -2095,7 +2095,6 @@
set_missing_values,
)
- doclist.set_onload("ignore_price_list", True)
return doclist
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 840a319..e9b71dd 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2629,6 +2629,7 @@
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
+ "voucher_detail_no": d.name,
},
raise_error_if_no_rate=False,
)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 5c18e50..1efe35c 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -195,7 +195,7 @@
company_address=None,
shipping_address=None,
*,
- ignore_permissions=False
+ ignore_permissions=False,
):
billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -239,7 +239,7 @@
shipping_address_display=render_address(
shipping_address, check_permissions=not ignore_permissions
),
- **get_fetch_values(doctype, "shipping_address", shipping_address)
+ **get_fetch_values(doctype, "shipping_address", shipping_address),
)
if party_details.company_address:
@@ -250,7 +250,7 @@
party_details.company_address_display
or render_address(party_details.company_address, check_permissions=False)
),
- **get_fetch_values(doctype, "billing_address", party_details.company_address)
+ **get_fetch_values(doctype, "billing_address", party_details.company_address),
)
# shipping address - if not already set
@@ -258,7 +258,7 @@
party_details.update(
shipping_address=party_details.billing_address,
shipping_address_display=party_details.billing_address_display,
- **get_fetch_values(doctype, "shipping_address", party_details.billing_address)
+ **get_fetch_values(doctype, "shipping_address", party_details.billing_address),
)
party_address, shipping_address = (
@@ -981,6 +981,9 @@
if party:
query = query.where(ple.party == party)
+ if invoice_doctypes := frappe.get_hooks("invoice_doctypes"):
+ query = query.where(ple.voucher_type.notin(invoice_doctypes))
+
data = query.run()
if data:
return frappe._dict(data)
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 7948e5f..eaf9f42 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -1087,7 +1087,7 @@
)
if self.filters.show_remarks:
- self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
+ self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200)
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
if not fieldname:
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 096bb10..7355c4b 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -8,7 +8,17 @@
import frappe
from frappe import _
-from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
+from frappe.utils import (
+ add_days,
+ add_months,
+ cint,
+ cstr,
+ flt,
+ formatdate,
+ get_first_day,
+ getdate,
+ today,
+)
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
@@ -43,6 +53,8 @@
year_start_date = getdate(period_start_date)
year_end_date = getdate(period_end_date)
+ year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
+
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_list = []
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index ad196a9..9c6e2d0 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -319,7 +319,7 @@
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account,
- `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
+ `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_group`,
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 595722d..2efb46e 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -608,6 +608,20 @@
return result
+ def update_ordered_qty_in_so_for_removed_items(self, removed_items):
+ """
+ Updates ordered_qty in linked SO when item rows are removed using Update Items
+ """
+ if not self.is_against_so():
+ return
+ for item in removed_items:
+ prev_ordered_qty = frappe.get_cached_value(
+ "Sales Order Item", item.get("sales_order_item"), "ordered_qty"
+ )
+ frappe.db.set_value(
+ "Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty
+ )
+
def auto_create_subcontracting_order(self):
if self.is_subcontracted and not self.is_old_subcontracting_flow:
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
@@ -699,8 +713,6 @@
set_missing_values,
)
- doc.set_onload("ignore_price_list", True)
-
return doc
@@ -780,7 +792,6 @@
postprocess,
ignore_permissions=ignore_permissions,
)
- doc.set_onload("ignore_price_list", True)
return doc
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index bb0c269..e2b737b 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -257,7 +257,6 @@
set_missing_values,
)
- doclist.set_onload("ignore_price_list", True)
return doclist
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index f551133..1b8f66c 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -2958,6 +2958,9 @@
d.cancel()
d.delete()
+ if parent.doctype == "Purchase Order":
+ parent.update_ordered_qty_in_so_for_removed_items(deleted_children)
+
# need to update ordered qty in Material Request first
# bin uses Material Request Items to recalculate & update
parent.update_prevdoc_status()
@@ -3248,7 +3251,10 @@
if parent_doctype == "Purchase Order":
update_last_purchase_rate(parent, is_submit=1)
- parent.update_prevdoc_status()
+
+ if any_qty_changed or items_added_or_removed or any_conversion_factor_changed:
+ parent.update_prevdoc_status()
+
parent.update_requested_qty()
parent.update_ordered_qty()
parent.update_ordered_and_reserved_qty()
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 54c97b4..3d863e9 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -118,6 +118,7 @@
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
+ "voucher_detail_no": row.name,
},
raise_error_if_no_rate=False,
)
@@ -373,6 +374,7 @@
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
+ "voucher_detail_no": d.name,
},
raise_error_if_no_rate=False,
)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 8e3a15a..81e71e3 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -586,8 +586,6 @@
set_missing_values,
)
- doclist.set_onload("ignore_price_list", True)
-
return doclist
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 5575a24..fdadb30 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -444,6 +444,7 @@
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
+ "voucher_detail_no": d.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
},
raise_error_if_no_rate=False,
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index b52fbad..9555902 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -880,6 +880,7 @@
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * item.consumed_qty,
+ "voucher_detail_no": item.name,
"serial_and_batch_bundle": item.serial_and_batch_bundle,
}
)
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index f0fc1aa..ef6cfb0 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -105,7 +105,7 @@
if self.source == "Existing Customer" and self.customer:
contact = frappe.db.get_value(
"Dynamic Link",
- {"link_doctype": "Customer", "link_name": self.customer},
+ {"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer},
"parent",
)
if contact:
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 4b1015d..d696cc4 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -261,8 +261,7 @@
# override capacity for employee
production_capacity = 1
- overlap_count = self.get_overlap_count(time_logs)
- if time_logs and production_capacity > overlap_count:
+ if not self.has_overlap(production_capacity, time_logs):
return {}
if self.workstation_type and time_logs:
@@ -272,16 +271,15 @@
return time_logs[-1]
- @staticmethod
- def get_overlap_count(time_logs):
- count = 1
+ def has_overlap(self, production_capacity, time_logs):
+ overlap = False
+ if production_capacity == 1 and len(time_logs) > 0:
+ return True
# Check overlap exists or not between the overlapping time logs with the current Job Card
- for idx, row in enumerate(time_logs):
- next_idx = idx
- if idx + 1 < len(time_logs):
- next_idx = idx + 1
- next_row = time_logs[next_idx]
+ for row in time_logs:
+ count = 1
+ for next_row in time_logs:
if row.name == next_row.name:
continue
@@ -301,7 +299,10 @@
):
count += 1
- return count
+ if count > production_capacity:
+ return True
+
+ return overlap
def get_time_logs(self, args, doctype, check_next_available_slot=False):
jc = frappe.qb.DocType("Job Card")
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 955821f..13ae3b3 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1597,19 +1597,23 @@
)
locations = get_available_item_locations(
- item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True
+ item.get("item_code"),
+ warehouses,
+ item.get("quantity") * item.get("conversion_factor"),
+ company,
+ ignore_validation=True,
)
required_qty = item.get("quantity")
+ if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"):
+ # Convert qty to stock UOM
+ required_qty = required_qty * item.get("conversion_factor")
+
# get available material by transferring to production warehouse
for d in locations:
if required_qty <= 0:
return
- conversion_factor = 1.0
- if purchase_uom != stock_uom and purchase_uom == item["uom"]:
- conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
-
new_dict = copy.deepcopy(item)
quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
@@ -1619,10 +1623,11 @@
"material_request_type": "Material Transfer",
"uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
"from_warehouse": d.get("warehouse"),
+ "conversion_factor": 1.0,
}
)
- required_qty -= quantity / conversion_factor
+ required_qty -= quantity
new_mr_items.append(new_dict)
# raise purchase request for remaining qty
@@ -1634,7 +1639,7 @@
if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
required_qty = ceil(required_qty)
- item["quantity"] = required_qty
+ item["quantity"] = required_qty / item.get("conversion_factor")
new_mr_items.append(item)
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index dd32c34..cc9d9a0 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1283,12 +1283,14 @@
for row in items:
row = frappe._dict(row)
if row.material_request_type == "Material Transfer":
+ self.assertTrue(row.uom == row.stock_uom)
self.assertTrue(row.from_warehouse in [wh1, wh2])
self.assertEqual(row.quantity, 2)
if row.material_request_type == "Purchase":
+ self.assertTrue(row.uom != row.stock_uom)
self.assertTrue(row.warehouse == mrp_warhouse)
- self.assertEqual(row.quantity, 12)
+ self.assertEqual(row.quantity, 12.0)
def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
@@ -1404,6 +1406,58 @@
self.assertEqual(after_qty, before_qty)
+ def test_material_request_qty_purchase_and_material_transfer(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+ bom_item = make_item(
+ properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
+ ).name
+
+ store_warehouse = create_warehouse("Store Warehouse", company="_Test Company")
+ rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
+
+ make_stock_entry(
+ item_code=bom_item,
+ qty=60,
+ target=store_warehouse,
+ rate=99,
+ )
+
+ if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
+ doc = frappe.get_doc("Item", bom_item)
+ doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
+ doc.save()
+
+ make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
+
+ pln = create_production_plan(
+ item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1
+ )
+
+ pln.for_warehouse = rm_warehouse
+ items = get_items_for_material_requests(
+ pln.as_dict(), warehouses=[{"warehouse": store_warehouse}]
+ )
+
+ for row in items:
+ self.assertEqual(row.get("quantity"), 10.0)
+ self.assertEqual(row.get("material_request_type"), "Material Transfer")
+ self.assertEqual(row.get("uom"), "_Test UOM 1")
+ self.assertEqual(row.get("from_warehouse"), store_warehouse)
+ self.assertEqual(row.get("conversion_factor"), 1.0)
+
+ items = get_items_for_material_requests(
+ pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}]
+ )
+
+ for row in items:
+ self.assertEqual(row.get("quantity"), 1.0)
+ self.assertEqual(row.get("material_request_type"), "Purchase")
+ self.assertEqual(row.get("uom"), "Nos")
+ self.assertEqual(row.get("conversion_factor"), 10.0)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 6dc24fa..8c0d84b 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -357,7 +357,7 @@
onload_post_render() {
if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
- && !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
+ && !this.frm.doc.__onload?.load_after_mapping) {
frappe.after_ajax(() => this.apply_default_taxes());
} else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"]
&& !this.frm.doc.is_pos) {
@@ -964,9 +964,9 @@
let me = this;
this.set_dynamic_labels();
let company_currency = this.get_company_currency();
- // Added `ignore_price_list` to determine if document is loading after mapping from another doc
+ // Added `load_after_mapping` to determine if document is loading after mapping from another doc
if(this.frm.doc.currency && this.frm.doc.currency !== company_currency
- && !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) {
+ && !this.frm.doc.__onload?.load_after_mapping) {
this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency,
function(exchange_rate) {
@@ -998,7 +998,7 @@
}
if(flt(this.frm.doc.conversion_rate)>0.0) {
- if(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
+ if(this.frm.doc.__onload?.load_after_mapping) {
this.calculate_taxes_and_totals();
} else if (!this.in_apply_price_list){
this.apply_price_list();
@@ -1085,9 +1085,9 @@
this.set_dynamic_labels();
var company_currency = this.get_company_currency();
- // Added `ignore_price_list` to determine if document is loading after mapping from another doc
+ // Added `load_after_mapping` to determine if document is loading after mapping from another doc
if(this.frm.doc.price_list_currency !== company_currency &&
- !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) {
+ !this.frm.doc.__onload?.load_after_mapping) {
this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency,
function(exchange_rate) {
me.frm.set_value("plc_conversion_rate", exchange_rate);
@@ -1476,7 +1476,7 @@
}
// Target doc created from a mapped doc
- if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
+ if (this.frm.doc.__onload?.load_after_mapping) {
// Calculate totals even though pricing rule is not applied.
// `apply_pricing_rule` is triggered due to change in data which most likely contributes to Total.
if (calculate_taxes_and_totals) me.calculate_taxes_and_totals();
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 9267801..0de6774 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -31,8 +31,19 @@
secondary_action: () => this.edit_full_form(),
});
- this.dialog.set_value("qty", this.item.qty);
+ this.dialog.set_value("qty", this.item.qty).then(() => {
+ if (this.item.serial_no) {
+ this.dialog.set_value("scan_serial_no", this.item.serial_no);
+ frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
+ } else if (this.item.batch_no) {
+ this.dialog.set_value("scan_batch_no", this.item.batch_no);
+ frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
+ }
+ });
+
this.dialog.show();
+ this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
+ this.$scan_btn.css("display", "inline");
}
get_serial_no_filters() {
@@ -95,6 +106,7 @@
if (this.item.has_serial_no) {
fields.push({
fieldtype: 'Data',
+ options: 'Barcode',
fieldname: 'scan_serial_no',
label: __('Scan Serial No'),
get_query: () => {
@@ -106,15 +118,10 @@
});
}
- if (this.item.has_batch_no && this.item.has_serial_no) {
- fields.push({
- fieldtype: 'Column Break',
- });
- }
-
- if (this.item.has_batch_no) {
+ if (this.item.has_batch_no && !this.item.has_serial_no) {
fields.push({
fieldtype: 'Data',
+ options: 'Barcode',
fieldname: 'scan_batch_no',
label: __('Scan Batch No'),
onchange: () => this.update_serial_batch_no()
@@ -309,6 +316,14 @@
}
get_auto_data() {
+ if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) {
+ return;
+ }
+
+ if (this.item.serial_no || this.item.batch_no) {
+ return;
+ }
+
let { qty, based_on } = this.dialog.get_values();
if (!based_on) {
@@ -340,16 +355,57 @@
const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
if (scan_serial_no) {
- this.dialog.fields_dict.entries.df.data.push({
- serial_no: scan_serial_no
+ let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+ if (d.serial_no === scan_serial_no) {
+ return d
+ }
});
- this.dialog.fields_dict.scan_serial_no.set_value('');
+ if (existing_row?.length) {
+ frappe.throw(__('Serial No {0} already exists', [scan_serial_no]));
+ }
+
+ if (!this.item.has_batch_no) {
+ this.dialog.fields_dict.entries.df.data.push({
+ serial_no: scan_serial_no
+ });
+
+ this.dialog.fields_dict.scan_serial_no.set_value('');
+ } else {
+ frappe.call({
+ method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_batch_no_from_serial_no',
+ args: {
+ serial_no: scan_serial_no,
+ },
+ callback: (r) => {
+ if (r.message) {
+ this.dialog.fields_dict.entries.df.data.push({
+ serial_no: scan_serial_no,
+ batch_no: r.message
+ });
+
+ this.dialog.fields_dict.scan_serial_no.set_value('');
+ }
+ }
+
+ })
+ }
} else if (scan_batch_no) {
- this.dialog.fields_dict.entries.df.data.push({
- batch_no: scan_batch_no
+ let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+ if (d.batch_no === scan_batch_no) {
+ return d
+ }
});
+ if (existing_row?.length) {
+ existing_row[0].qty += 1;
+ } else {
+ this.dialog.fields_dict.entries.df.data.push({
+ batch_no: scan_batch_no,
+ qty: 1
+ });
+ }
+
this.dialog.fields_dict.scan_batch_no.set_value('');
}
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 3b97123..31bbbcf 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -665,7 +665,7 @@
"search_fields": "customer_name,customer_group,territory, mobile_no,primary_address",
"show_name_in_global_search": 1,
"sort_field": "modified",
- "sort_order": "ASC",
+ "sort_order": "DESC",
"states": [],
"title_field": "customer_name",
"track_changes": 1
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 96df0ed..efb9820 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -587,7 +587,8 @@
"""
select sum(debit) - sum(credit)
from `tabGL Entry` where party_type = 'Customer'
- and party = %s and company=%s {0}""".format(
+ and is_cancelled = 0 and party = %s
+ and company=%s {0}""".format(
cond
),
(customer, company),
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 8d3bb78..00b79e3 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -434,9 +434,6 @@
ignore_permissions=ignore_permissions,
)
- # postprocess: fetch shipping address, set missing values
- doclist.set_onload("ignore_price_list", True)
-
return doclist
@@ -505,8 +502,6 @@
ignore_permissions=ignore_permissions,
)
- doclist.set_onload("ignore_price_list", True)
-
return doclist
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index fd6c027..09941ea 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -995,7 +995,6 @@
# Should be called after mapping items.
set_missing_values(so, target_doc)
- target_doc.set_onload("ignore_price_list", True)
return target_doc
@@ -1085,8 +1084,6 @@
if automatically_fetch_payment_terms:
doclist.set_payment_schedule()
- doclist.set_onload("ignore_price_list", True)
-
return doclist
diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
index cb6e8a1..9f3ba0d 100644
--- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
+++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
@@ -3,7 +3,8 @@
import frappe
-from frappe import _, msgprint
+from frappe import _, msgprint, qb
+from frappe.query_builder import Criterion
from erpnext import get_company_currency
@@ -214,24 +215,33 @@
if items:
conditions.append("dt_item.item_code in (%s)" % ", ".join(["%s"] * len(items)))
values += items
+ else:
+ # return empty result, if no items are fetched after filtering on 'item group' and 'brand'
+ conditions.append("dt_item.item_code = Null")
return " and ".join(conditions), values
def get_items(filters):
+ item = qb.DocType("Item")
+
+ item_query_conditions = []
if filters.get("item_group"):
- key = "item_group"
- elif filters.get("brand"):
- key = "brand"
- else:
- key = ""
-
- items = []
- if key:
- items = frappe.db.sql_list(
- """select name from tabItem where %s = %s""" % (key, "%s"), (filters[key])
+ # Handle 'Parent' nodes as well.
+ item_group = qb.DocType("Item Group")
+ lft, rgt = frappe.db.get_all(
+ "Item Group", filters={"name": filters.get("item_group")}, fields=["lft", "rgt"], as_list=True
+ )[0]
+ item_group_query = (
+ qb.from_(item_group)
+ .select(item_group.name)
+ .where((item_group.lft >= lft) & (item_group.rgt <= rgt))
)
+ item_query_conditions.append(item.item_group.isin(item_group_query))
+ if filters.get("brand"):
+ item_query_conditions.append(item.brand == filters.get("brand"))
+ items = qb.from_(item).select(item.name).where(Criterion.all(item_query_conditions)).run()
return items
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index e44736b..a101bdf 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -964,8 +964,6 @@
if automatically_fetch_payment_terms:
doc.set_payment_schedule()
- doc.set_onload("ignore_price_list", True)
-
return doc
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 907bc67..ab07271 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -1208,7 +1208,6 @@
set_missing_values,
)
- doclist.set_onload("ignore_price_list", True)
return doclist
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index f0cff21..108a2e0 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -252,6 +252,7 @@
"serial_nos": [row.serial_no for row in self.entries if row.serial_no],
"batch_nos": {row.batch_no: row for row in self.entries if row.batch_no},
"voucher_type": self.voucher_type,
+ "voucher_detail_no": self.voucher_detail_no,
}
)
@@ -1744,3 +1745,8 @@
batches[key].qty += d.qty
return batches
+
+
+@frappe.whitelist()
+def get_batch_no_from_serial_no(serial_no):
+ return frappe.get_cached_value("Serial No", serial_no, "batch_no")
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8158b99..3baafd7 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -883,6 +883,7 @@
"company": self.company,
"allow_zero_valuation": item.allow_zero_valuation_rate,
"serial_and_batch_bundle": item.serial_and_batch_bundle,
+ "voucher_detail_no": item.name,
}
)
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index b640983..eb1c7a8 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -936,6 +936,42 @@
stock_entry.insert()
self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items])
+ def test_nagative_stock_for_batch(self):
+ item = make_item(
+ "_Test Batch Negative Item",
+ {
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "B-BATCH-.##",
+ "is_stock_item": 1,
+ },
+ )
+
+ make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100)
+
+ ste = frappe.new_doc("Stock Entry")
+ ste.purpose = "Material Issue"
+ ste.company = "_Test Company"
+ for qty in [50, 20, 30]:
+ ste.append(
+ "items",
+ {
+ "item_code": item.name,
+ "s_warehouse": "_Test Warehouse - _TC",
+ "qty": qty,
+ "uom": item.stock_uom,
+ "stock_uom": item.stock_uom,
+ "conversion_factor": 1,
+ "transfer_qty": qty,
+ },
+ )
+
+ ste.set_stock_entry_type()
+ ste.insert()
+ make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100)
+
+ self.assertRaises(frappe.ValidationError, ste.submit)
+
def test_same_serial_nos_in_repack_or_manufacture_entries(self):
s1 = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
serial_nos = get_serial_nos_from_bundle(s1.get("items")[0].serial_and_batch_bundle)
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index f7c6ffe..d8a3f2e 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -485,9 +485,9 @@
dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"])
svd_list = [-1 * d["stock_value_difference"] for d in sle_details]
- expected_incoming_rates = expected_abs_svd = sorted([75.0, 125.0, 75.0, 125.0])
+ expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0]
- self.assertEqual(expected_abs_svd, sorted(svd_list), "Incorrect 'Stock Value Difference' values")
+ self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values")
for dn, incoming_rate in zip(dns, expected_incoming_rates):
self.assertTrue(
dn.items[0].incoming_rate in expected_abs_svd,
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js
index ea7bf56..e033fd9 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.js
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.js
@@ -17,6 +17,7 @@
fieldtype: "Link",
options:"Item",
default: "",
+ get_query: () => ({filters: { 'is_stock_item': 1 }}),
},
{
fieldname: "value_quantity",
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index 6c5b58c..ab48181 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -270,7 +270,7 @@
if item_code := filters.get("item_code"):
return [item_code]
else:
- item_filters = {}
+ item_filters = {"is_stock_item": 1}
if item_group := filters.get("item_group"):
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
item_filters["item_group"] = ("in", children + [item_group])
diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
index b1e4a74..bf3a397 100644
--- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
+++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
@@ -14,9 +14,17 @@
frappe.query_reports["Stock Ledger Variance"] = {
"filters": [
{
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
"fieldname": "item_code",
"fieldtype": "Link",
- "label": "Item",
+ "label": __("Item"),
"options": "Item",
get_query: function() {
return {
@@ -27,7 +35,7 @@
{
"fieldname": "warehouse",
"fieldtype": "Link",
- "label": "Warehouse",
+ "label": __("Warehouse"),
"options": "Warehouse",
get_query: function() {
return {
@@ -38,7 +46,7 @@
{
"fieldname": "difference_in",
"fieldtype": "Select",
- "label": "Difference In",
+ "label": __("Difference In"),
"options": [
"",
"Qty",
@@ -49,7 +57,7 @@
{
"fieldname": "include_disabled",
"fieldtype": "Check",
- "label": "Include Disabled",
+ "label": __("Include Disabled"),
}
],
diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
index 732f108..acbbe90 100644
--- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
+++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
@@ -230,7 +230,12 @@
bin.item_code,
bin.warehouse,
)
- .where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
+ .where(
+ (item.is_stock_item == 1)
+ & (item.has_serial_no == 0)
+ & (warehouse.is_group == 0)
+ & (warehouse.company == filters.company)
+ )
)
if filters.item_code:
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index de28be1..0c18792 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -406,7 +406,7 @@
.orderby(bundle.posting_date, bundle.posting_time, bundle.creation)
)
- # Important to exclude the current voucher
+ # Important to exclude the current voucher to calculate correct the stock value difference
if self.sle.voucher_no:
query = query.where(bundle.voucher_no != self.sle.voucher_no)
@@ -539,8 +539,10 @@
.groupby(child.batch_no)
)
- # Important to exclude the current voucher
- if self.sle.voucher_no:
+ # Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference
+ if self.sle.voucher_detail_no:
+ query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no)
+ elif self.sle.voucher_no:
query = query.where(parent.voucher_no != self.sle.voucher_no)
if timestamp_condition:
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 73755be..2745d4d 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -8856,3 +8856,7 @@
Split Early Payment Discount Loss into Income and Tax Loss,"Skontobetrag in Aufwand und Umsatzsteuerkorrektur aufteilen",
Approve,Genehmigen,
Reject,Ablehnen,
+Lost Quotations,Verlorene Angebote,
+Lost Quotations %,Verlorene Angebote %,
+Lost Value,Verlorener Wert,
+Lost Value %,Verlorener Wert %,
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 5c34759..a711123 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -134,7 +134,7 @@
Advertising,Publicité,
Aerospace,Aérospatial,
Against,Contre,
-Against Account,Pour le Compte,
+Against Account,Contrepartie,
Against Journal Entry {0} does not have any unmatched {1} entry,L'Écriture de Journal {0} n'a pas d'entrée non associée {1},
Against Journal Entry {0} is already adjusted against some other voucher,L'Écriture de Journal {0} est déjà ajustée par un autre bon,
Against Supplier Invoice {0} dated {1},Pour la Facture Fournisseur {0} datée {1},
@@ -2969,7 +2969,7 @@
Please save first,S'il vous plaît enregistrer en premier,
Price not found for item {0} in price list {1},Prix non trouvé pour l'article {0} dans la liste de prix {1},
Warehouse Type,Type d'entrepôt,
-'Date' is required,'Date' est requis,
+'Date' is required,La 'date' est obligatoire,
Budgets,Budgets,
Bundle Qty,Quantité de paquet,
Company GSTIN,GSTIN de la Société,
@@ -3002,7 +3002,7 @@
Account: {0} is not permitted under Payment Entry,Compte: {0} n'est pas autorisé sous Saisie du paiement.,
Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.,La dimension de comptabilité <b>{0}</b> est requise pour le compte "Bilan" {1}.,
Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.,La dimension de comptabilité <b>{0}</b> est requise pour le compte 'Bénéfices et pertes' {1}.,
-Accounting Masters,Maîtres Comptables,
+Accounting Masters,Données de base,
Accounting Period overlaps with {0},La période comptable chevauche avec {0},
Add to Featured Item,Ajouter à l'article en vedette,
Add your review,Ajouter votre avis,
@@ -3701,7 +3701,7 @@
Accounting Period,Période comptable,
Period Name,Nom de période,
Closed Documents,Documents fermés,
-Accounts Settings,Paramètres des Comptes,
+Accounts Settings,Paramètres de comptabilité,
Settings for Accounts,Paramètres des Comptes,
Make Accounting Entry For Every Stock Movement,Faites une Écriture Comptable Pour Chaque Mouvement du Stock,
Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,Les utilisateurs ayant ce rôle sont autorisés à définir les comptes gelés et à créer / modifier des écritures comptables sur des comptes gelés,
@@ -3718,7 +3718,7 @@
Currency Exchange Settings,Paramètres d'échange de devises,
Allow Stale Exchange Rates,Autoriser les Taux de Change Existants,
Stale Days,Journées Passées,
-Report Settings,Paramètres de rapport,
+Report Settings,Paramètres des rapports,
Use Custom Cash Flow Format,Utiliser le format de flux de trésorerie personnalisé,
Allowed To Transact With,Autorisé à faire affaire avec,
SWIFT number,Numéro rapide,
@@ -4321,7 +4321,7 @@
Redemption Cost Center,Centre de coûts pour l'échange,
In Words will be visible once you save the Sales Invoice.,En Toutes Lettres. Sera visible une fois que vous enregistrerez la Facture.,
Allocate Advances Automatically (FIFO),Allouer automatiquement les avances (FIFO),
-Get Advances Received,Obtenir Acomptes Reçus,
+Get Advances Received,Obtenir les paiements des avances,
Base Change Amount (Company Currency),Montant de Base à Rendre (Devise de la Société),
Write Off Outstanding Amount,Encours de Reprise,
Terms and Conditions Details,Détails des Termes et Conditions,
@@ -4329,7 +4329,7 @@
Is Discounted,Est réduit,
Unpaid and Discounted,Non payé et à prix réduit,
Overdue and Discounted,En retard et à prix réduit,
-Accounting Details,Détails Comptabilité,
+Accounting Details,Détails Comptable,
Debit To,Débit Pour,
Is Opening Entry,Est Écriture Ouverte,
C-Form Applicable,Formulaire-C Applicable,
@@ -8028,7 +8028,7 @@
Completed Qty cannot be greater than 'Qty to Manufacture',La quantité terminée ne peut pas être supérieure à la `` quantité à fabriquer '',
"Row {0}: For Supplier {1}, Email Address is Required to send an email","Ligne {0}: pour le fournisseur {1}, l'adresse e-mail est obligatoire pour envoyer un e-mail",
"If enabled, the system will post accounting entries for inventory automatically","Si activé, le système enregistrera automatiquement les écritures comptables pour l'inventaire",
-Accounts Frozen Till Date,Comptes gelés jusqu'à la date,
+Accounts Frozen Till Date,Comptes gelés jusqu'au,
Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below,Les écritures comptables sont gelées jusqu'à cette date. Personne ne peut créer ou modifier des entrées sauf les utilisateurs avec le rôle spécifié ci-dessous,
Role Allowed to Set Frozen Accounts and Edit Frozen Entries,Rôle autorisé à définir des comptes gelés et à modifier les entrées gelées,
Address used to determine Tax Category in transactions,Adresse utilisée pour déterminer la catégorie de taxe dans les transactions,
@@ -8339,7 +8339,7 @@
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,
+Quick Access, Accès rapides,
{} Available,{} Disponible.s,
{} Pending,{} En attente.s,
{} To Bill,{} à facturer,
@@ -8449,3 +8449,64 @@
Allow Sales,Autoriser à la vente
Approve,Approuver,
Reject,Rejeter,
+'Account' in the Accounting section of Customer {0},'Compte' dans la section comptabilité du client {0},
+Accounting Entry Number,Numéro d'écriture comptable,
+Accounting Ledger,Grand livre,
+Accounts Closing,Clôture,
+Accounts Frozen Upto,Comptes gelés jusqu'au,
+Accounts Manager,Responsable comptable,
+Active Customers,Clients actifs,
+Against Account,Contrepartie,
+All the Comments and Emails will be copied from one document to another newly created document(Lead -> Opportunity -> Quotation) throughout the CRM documents.,Tous les commentaires et les courriels seront copiés d'un document à un autre document nouvellement créé (Lead -> Opportunité -> Devis) dans l'ensemble des documents CRM.,
+Allow multi-currency invoices against single party account ,Autoriser les factures multi-devises en contrepartie d'un seul compte de tiers,
+Allow Sales Order Creation For Expired Quotation,Autoriser la création de commandes client pour les devis expirés,
+Allow Continuous Material Consumption,Autoriser la consommation continue de matériel,
+Allow Lead Duplication based on Emails,Autoriser la duplication des pistes sur la base des courriels,
+Asset Settings,Paramètres des actifs,
+Auto close Opportunity Replied after the no. of days mentioned above,Fermeture automatique de l'opportunité de réponse après le nombre de jours mentionné ci-dessus.,
+Auto Creation of Contact,Création automatique d'un contact,
+Automatically Fetch Payment Terms from Order,Récupération automatique des conditions de paiement de la commande,
+Bill for Rejected Quantity in Purchase Invoice,Facturation de la quantité rejetée dans la facture d'achat,
+Credit Limit Settings,Paramètres de la limite de crédit,
+Create Ledger Entries for Change Amount,Créer des écritures de grand livre pour modifier le montant,
+Customer Defaults,Valeurs par défaut des clients,
+Calculate Product Bundle Price based on Child Items' Rates,Calculer le prix des ensembles de produits en fonction des tarifs des articles enfants,
+Configure the action to stop the transaction or just warn if the same rate is not maintained.,Configurez une action pour stopper la transaction ou alertez simplement su le prix unitaie n'est pas maintenu.,
+Close Replied Opportunity After Days,Fermer l'opportunité répliquée après des jours,
+Carry Forward Communication and Comments,Reprendre les communications et commentaires,
+Default Down Payment Payable Account,Compte d'acompte fournisseur par défaut,
+Default Down Payment Receivable Account,Compte d'acompte client par défaut,
+Disable Last Purchase Rate,Désactiver le dernier prix d'achat,
+'Default {0} Account' in Company {1},'Compte {0} par défaut' dans la société {1},
+Enable Custom Cash Flow Format,Activation du format de flux de trésorerie personnalisé,
+Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field,Garanti que chaque facture d'achat est associée à un numéro de facture fournisseur unique,
+Enable Common Party Accounting,Activer la comptabilité des tiers communs,
+Enabling this will allow creation of multi-currency invoices against single party account in company currency,L'activation de cette option va permettre la création de factures multi-devises en contrepartie d'un seul compte de tiers en devise de la société,
+Enable Discount Accounting for Selling,Activation de la comptabilité d'escompte pour la vente,
+'Expected Start Date' can not be greater than 'Expected End Date','Date de Début Prévue' ne peut pas être postérieure à 'Date de Fin Prévue',
+Get Advances Received, Obtenir les paiements des avances,
+"If enabled, ledger entries will be posted for change amount in POS transactions","Si cette option est activée, des écritures de grand livre seront enregistrées pour le montant de la modification dans les transactions POS.",
+"If enabled, additional ledger entries will be made for discounts in a separate Discount Account","Si cette option est activée, des écritures de grand livre supplémentaires seront effectuées pour les remises dans un compte de remise séparé.",
+Item Price Settings,Paramètres du prix de l'article,
+Invoice and Billing,Facturation,
+Invoice Cancellation,Annulation de facture,
+Invoicing Features,Caractéristiques de la facturation,
+Journals,Journaux,
+"Learn about <a href=""https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier."">Common Party</a>","En savoir plus <a href=""https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier."">Tiers communs</a>",
+Naming Series and Price Defaults,Nom de série et Tarifs,
+Over Order Allowance (%),Tolérance de sur-commande (%),
+Payment Terms from orders will be fetched into the invoices as is,Les termes de paiement des commandes seront récupérées dans les factures telles quelles,
+Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.,"Percentage de commande autorisée en plus de la quantité prévue dans la commande ouverte. Par exemple: Si vous avez une commande avec une quantité de 100 unités et une tolérance de 10%, alors vous pourrez commander jusqu'à 110 unités.",
+Period Closing Settings,Paramètres de clôture de la période,
+Quick Access,Accès rapide,
+Report Setting,Réglage des rapports,
+Record all transactions against an accounting journal,Comptabiliser toutes les transactions dans un journal comptable,
+Rows with Same Account heads will be merged on Ledger,Les lignes associées aux mêmes comptes comptables seront fusionnées dans le grand livre,
+Role Allowed to Over Bill ,Rôle autorisé à sur-facturer,
+Role allowed to bypass Credit Limit,Rôle autorisé à contourner la limite de crédit,
+Role Allowed to Override Stop Action,Rôle autorisé à outrepasser l'action Stop,
+Show Balances in Chart Of Accounts,Afficher les soldes dans le plan comptable,
+Sales Update Frequency in Company and Project,Fréquence de mise à jour des ventes dans la société et le projet,
+Transaction Settings,Paramètres des transactions,
+Subcontracting Settings,Paramètres de sous-traitance,
+Users with this role are allowed to over bill above the allowance percentage,Les utilisateurs avec ce rôle sont autorisés à sur-facturer au delà du pourcentage de tolérance,
diff --git a/erpnext/utilities/doctype/sms_log/README.md b/erpnext/utilities/doctype/sms_log/README.md
deleted file mode 100644
index 9ee2b79..0000000
--- a/erpnext/utilities/doctype/sms_log/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Log of SMS sent via SMS Center.
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/sms_log/__init__.py b/erpnext/utilities/doctype/sms_log/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/utilities/doctype/sms_log/__init__.py
+++ /dev/null
diff --git a/erpnext/utilities/doctype/sms_log/sms_log.js b/erpnext/utilities/doctype/sms_log/sms_log.js
deleted file mode 100644
index f5358e8..0000000
--- a/erpnext/utilities/doctype/sms_log/sms_log.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('SMS Log', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/utilities/doctype/sms_log/sms_log.json b/erpnext/utilities/doctype/sms_log/sms_log.json
deleted file mode 100644
index 269094b..0000000
--- a/erpnext/utilities/doctype/sms_log/sms_log.json
+++ /dev/null
@@ -1,371 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "SYS-SMS-.#####",
- "beta": 0,
- "creation": "2012-03-27 14:36:47",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 0,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sender_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sender Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sent_on",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sent On",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break0",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
- "width": "50%"
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "message",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Message",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sec_break1",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "options": "Simple",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_requested_sms",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "No of Requested SMS",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "requested_numbers",
- "fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Requested Numbers",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break1",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
- "width": "50%"
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_sent_sms",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "No of Sent SMS",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sent_to",
- "fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sent To",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-mobile-phone",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-21 16:15:40.898889",
- "modified_by": "Administrator",
- "module": "Utilities",
- "name": "SMS Log",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/sms_log/sms_log.py b/erpnext/utilities/doctype/sms_log/sms_log.py
deleted file mode 100644
index 8e4c248..0000000
--- a/erpnext/utilities/doctype/sms_log/sms_log.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-from frappe.model.document import Document
-
-
-class SMSLog(Document):
- # begin: auto-generated types
- # This code is auto-generated. Do not modify anything in this block.
-
- from typing import TYPE_CHECKING
-
- if TYPE_CHECKING:
- from frappe.types import DF
-
- message: DF.SmallText | None
- no_of_requested_sms: DF.Int
- no_of_sent_sms: DF.Int
- requested_numbers: DF.Code | None
- sender_name: DF.Data | None
- sent_on: DF.Date | None
- sent_to: DF.Code | None
- # end: auto-generated types
-
- pass
diff --git a/erpnext/utilities/doctype/sms_log/test_sms_log.py b/erpnext/utilities/doctype/sms_log/test_sms_log.py
deleted file mode 100644
index 3ff0202..0000000
--- a/erpnext/utilities/doctype/sms_log/test_sms_log.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-# test_records = frappe.get_test_records('SMS Log')
-
-
-class TestSMSLog(unittest.TestCase):
- pass