Merge branch 'develop' of https://github.com/frappe/erpnext into editable_inovice
This commit is contained in:
commit
be0f6b3e6e
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,9 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
},
|
||||
};
|
||||
});
|
||||
let no_bank_transactions_text =
|
||||
`<div class="text-muted text-center">${__("No Matching Bank Transactions Found")}</div>`
|
||||
set_field_options("no_bank_transactions", no_bank_transactions_text);
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
|
@ -81,8 +81,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "no_bank_transactions",
|
||||
"fieldtype": "HTML",
|
||||
"options": "<div class=\"text-muted text-center\">No Matching Bank Transactions Found</div>"
|
||||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@ -109,4 +108,4 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
|
||||
if (frm.doc.status.includes("Success")) {
|
||||
frm.add_custom_button(
|
||||
__("Go to {0} List", [frm.doc.reference_doctype]),
|
||||
__("Go to {0} List", [__(frm.doc.reference_doctype)]),
|
||||
() => frappe.set_route("List", frm.doc.reference_doctype)
|
||||
);
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ class Budget(Document):
|
||||
self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###"
|
||||
|
||||
|
||||
def validate_expense_against_budget(args):
|
||||
def validate_expense_against_budget(args, expense_amount=0):
|
||||
args = frappe._dict(args)
|
||||
|
||||
if args.get("company") and not args.fiscal_year:
|
||||
@ -175,13 +175,13 @@ def validate_expense_against_budget(args):
|
||||
) # nosec
|
||||
|
||||
if budget_records:
|
||||
validate_budget_records(args, budget_records)
|
||||
validate_budget_records(args, budget_records, expense_amount)
|
||||
|
||||
|
||||
def validate_budget_records(args, budget_records):
|
||||
def validate_budget_records(args, budget_records, expense_amount):
|
||||
for budget in budget_records:
|
||||
if flt(budget.budget_amount):
|
||||
amount = get_amount(args, budget)
|
||||
amount = expense_amount or get_amount(args, budget)
|
||||
yearly_action, monthly_action = get_actions(args, budget)
|
||||
|
||||
if monthly_action in ["Stop", "Warn"]:
|
||||
|
@ -334,6 +334,39 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
jv.cancel()
|
||||
|
||||
def test_monthly_budget_against_main_cost_center(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.accounts.doctype.cost_center_allocation.test_cost_center_allocation import (
|
||||
create_cost_center_allocation,
|
||||
)
|
||||
|
||||
cost_centers = [
|
||||
"Main Budget Cost Center 1",
|
||||
"Sub Budget Cost Center 1",
|
||||
"Sub Budget Cost Center 2",
|
||||
]
|
||||
|
||||
for cc in cost_centers:
|
||||
create_cost_center(cost_center_name=cc, company="_Test Company")
|
||||
|
||||
create_cost_center_allocation(
|
||||
"_Test Company",
|
||||
"Main Budget Cost Center 1 - _TC",
|
||||
{"Sub Budget Cost Center 1 - _TC": 60, "Sub Budget Cost Center 2 - _TC": 40},
|
||||
)
|
||||
|
||||
make_budget(budget_against="Cost Center", cost_center="Main Budget Cost Center 1 - _TC")
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
400000,
|
||||
"Main Budget Cost Center 1 - _TC",
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
|
||||
self.assertRaises(BudgetError, jv.submit)
|
||||
|
||||
|
||||
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
|
||||
if budget_against_field == "project":
|
||||
|
@ -312,8 +312,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
}
|
||||
}
|
||||
|
||||
get_outstanding(doctype, docname, company, child, due_date) {
|
||||
var me = this;
|
||||
get_outstanding(doctype, docname, company, child) {
|
||||
var args = {
|
||||
"doctype": doctype,
|
||||
"docname": docname,
|
||||
|
@ -1210,6 +1210,7 @@ def get_outstanding(args):
|
||||
args = json.loads(args)
|
||||
|
||||
company_currency = erpnext.get_company_currency(args.get("company"))
|
||||
due_date = None
|
||||
|
||||
if args.get("doctype") == "Journal Entry":
|
||||
condition = " and party=%(party)s" if args.get("party") else ""
|
||||
@ -1234,10 +1235,12 @@ def get_outstanding(args):
|
||||
invoice = frappe.db.get_value(
|
||||
args["doctype"],
|
||||
args["docname"],
|
||||
["outstanding_amount", "conversion_rate", scrub(party_type)],
|
||||
["outstanding_amount", "conversion_rate", scrub(party_type), "due_date"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
due_date = invoice.get("due_date")
|
||||
|
||||
exchange_rate = (
|
||||
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
|
||||
)
|
||||
@ -1260,6 +1263,7 @@ def get_outstanding(args):
|
||||
"exchange_rate": exchange_rate,
|
||||
"party_type": party_type,
|
||||
"party": invoice.get(scrub(party_type)),
|
||||
"reference_due_date": due_date,
|
||||
}
|
||||
|
||||
|
||||
|
@ -216,7 +216,7 @@
|
||||
{
|
||||
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
||||
"fieldname": "reference_due_date",
|
||||
"fieldtype": "Select",
|
||||
"fieldtype": "Date",
|
||||
"label": "Reference Due Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
@ -284,7 +284,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-13 17:07:17.999191",
|
||||
"modified": "2022-10-26 20:03:10.906259",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"barcode",
|
||||
"has_item_scanned",
|
||||
"item_code",
|
||||
"col_break1",
|
||||
"item_name",
|
||||
@ -808,11 +809,19 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Grant Commission",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "barcode",
|
||||
"fieldname": "has_item_scanned",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-10-05 12:23:47.506290",
|
||||
"modified": "2022-11-02 12:52:39.125295",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
@ -820,5 +829,6 @@
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
@ -52,7 +52,10 @@
|
||||
"free_item_rate",
|
||||
"column_break_42",
|
||||
"free_item_uom",
|
||||
"round_free_qty",
|
||||
"is_recursive",
|
||||
"recurse_for",
|
||||
"apply_recursion_over",
|
||||
"section_break_23",
|
||||
"valid_from",
|
||||
"valid_upto",
|
||||
@ -578,12 +581,34 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Naming Series",
|
||||
"options": "PRLE-.####"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "round_free_qty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Free Qty"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_recursive",
|
||||
"description": "Give free item for every N quantity",
|
||||
"fieldname": "recurse_for",
|
||||
"fieldtype": "Float",
|
||||
"label": "Recurse Every (As Per Transaction UOM)",
|
||||
"mandatory_depends_on": "is_recursive"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "is_recursive",
|
||||
"description": "Qty for which recursion isn't applicable.",
|
||||
"fieldname": "apply_recursion_over",
|
||||
"fieldtype": "Float",
|
||||
"label": "Apply Recursion Over (As Per Transaction UOM)"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-16 16:00:38.356266",
|
||||
"modified": "2022-10-13 19:05:35.056304",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
@ -24,6 +24,7 @@ class PricingRule(Document):
|
||||
self.validate_applicable_for_selling_or_buying()
|
||||
self.validate_min_max_amt()
|
||||
self.validate_min_max_qty()
|
||||
self.validate_recursion()
|
||||
self.cleanup_fields_value()
|
||||
self.validate_rate_or_discount()
|
||||
self.validate_max_discount()
|
||||
@ -109,6 +110,18 @@ class PricingRule(Document):
|
||||
if self.min_amt and self.max_amt and flt(self.min_amt) > flt(self.max_amt):
|
||||
throw(_("Min Amt can not be greater than Max Amt"))
|
||||
|
||||
def validate_recursion(self):
|
||||
if self.price_or_product_discount != "Product":
|
||||
return
|
||||
if self.free_item or self.same_item:
|
||||
if flt(self.recurse_for) <= 0:
|
||||
self.recurse_for = 1
|
||||
if self.is_recursive:
|
||||
if flt(self.apply_recursion_over) > flt(self.min_qty):
|
||||
throw(_("Min Qty should be greater than Recurse Over Qty"))
|
||||
if flt(self.apply_recursion_over) < 0:
|
||||
throw(_("Recurse Over Qty cannot be less than 0"))
|
||||
|
||||
def cleanup_fields_value(self):
|
||||
for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]:
|
||||
fieldname = frappe.scrub(self.get(logic_field) or "")
|
||||
|
@ -710,6 +710,132 @@ class TestPricingRule(unittest.TestCase):
|
||||
|
||||
item.delete()
|
||||
|
||||
def test_item_group_price_with_blank_uom_pricing_rule(self):
|
||||
group = frappe.get_doc(doctype="Item Group", item_group_name="_Test Pricing Rule Item Group")
|
||||
group.save()
|
||||
properties = {
|
||||
"item_code": "Item with Group Blank UOM",
|
||||
"item_group": "_Test Pricing Rule Item Group",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item with Group Blank UOM", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item with Group Blank UOM Rule",
|
||||
"apply_on": "Item Group",
|
||||
"item_groups": [
|
||||
{
|
||||
"item_group": "_Test Pricing Rule Item Group",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"rate_or_discount": "Rate",
|
||||
"rate": 101,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
do_not_save=True, item_code="Item with Group Blank UOM", uom="Box", conversion_factor=10
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
|
||||
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
|
||||
self.assertEqual(si.items[0].price_list_rate, 1010)
|
||||
self.assertEqual(si.items[0].rate, 1010)
|
||||
|
||||
si.delete()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True, item_code="Item with Group Blank UOM", uom="Nos")
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
|
||||
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
|
||||
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||
self.assertEqual(si.items[0].rate, 101)
|
||||
|
||||
si.delete()
|
||||
rule.delete()
|
||||
frappe.get_doc("Item Price", {"item_code": "Item with Group Blank UOM"}).delete()
|
||||
item.delete()
|
||||
group.delete()
|
||||
|
||||
def test_item_group_price_with_selling_uom_pricing_rule(self):
|
||||
group = frappe.get_doc(doctype="Item Group", item_group_name="_Test Pricing Rule Item Group UOM")
|
||||
group.save()
|
||||
properties = {
|
||||
"item_code": "Item with Group UOM other than Stock",
|
||||
"item_group": "_Test Pricing Rule Item Group UOM",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item with Group UOM other than Stock", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item with Group UOM other than Stock Rule",
|
||||
"apply_on": "Item Group",
|
||||
"item_groups": [
|
||||
{
|
||||
"item_group": "_Test Pricing Rule Item Group UOM",
|
||||
"uom": "Box",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"rate_or_discount": "Rate",
|
||||
"rate": 101,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
do_not_save=True,
|
||||
item_code="Item with Group UOM other than Stock",
|
||||
uom="Box",
|
||||
conversion_factor=10,
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is Box so apply pricing_rule only on Box UOM.
|
||||
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
|
||||
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||
self.assertEqual(si.items[0].rate, 101)
|
||||
|
||||
si.delete()
|
||||
|
||||
si = create_sales_invoice(
|
||||
do_not_save=True, item_code="Item with Group UOM other than Stock", uom="Nos"
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
|
||||
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
|
||||
self.assertEqual(si.items[0].price_list_rate, 100)
|
||||
self.assertEqual(si.items[0].rate, 100)
|
||||
|
||||
si.delete()
|
||||
rule.delete()
|
||||
frappe.get_doc("Item Price", {"item_code": "Item with Group UOM other than Stock"}).delete()
|
||||
item.delete()
|
||||
group.delete()
|
||||
|
||||
def test_pricing_rule_for_different_currency(self):
|
||||
make_item("Test Sanitizer Item")
|
||||
|
||||
@ -943,6 +1069,45 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.delete()
|
||||
rule.delete()
|
||||
|
||||
def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"rate": 0,
|
||||
"min_qty": 3,
|
||||
"max_qty": 7,
|
||||
"price_or_product_discount": "Product",
|
||||
"same_item": 1,
|
||||
"free_qty": 1,
|
||||
"round_free_qty": 1,
|
||||
"is_recursive": 1,
|
||||
"recurse_for": 2,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
# With pricing rule
|
||||
so = make_sales_order(item_code="_Test Item", qty=5)
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||
self.assertEqual(so.items[1].qty, 2)
|
||||
|
||||
so = make_sales_order(item_code="_Test Item", qty=7)
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||
self.assertEqual(so.items[1].qty, 4)
|
||||
|
||||
|
||||
test_dependencies = ["Campaign"]
|
||||
|
||||
|
@ -127,6 +127,12 @@ def _get_pricing_rules(apply_on, args, values):
|
||||
values["variant_of"] = args.variant_of
|
||||
elif apply_on_field == "item_group":
|
||||
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
|
||||
if args.get("uom", None):
|
||||
item_conditions += (
|
||||
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
|
||||
child_doc=child_doc, item_uom=args.get("uom")
|
||||
)
|
||||
)
|
||||
|
||||
conditions += get_other_conditions(conditions, values, args)
|
||||
warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`")
|
||||
@ -627,9 +633,13 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
|
||||
qty = pricing_rule.free_qty or 1
|
||||
if pricing_rule.is_recursive:
|
||||
transaction_qty = args.get("qty") if args else doc.total_qty
|
||||
transaction_qty = (
|
||||
args.get("qty") if args else doc.total_qty
|
||||
) - pricing_rule.apply_recursion_over
|
||||
if transaction_qty:
|
||||
qty = flt(transaction_qty) * qty
|
||||
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
|
||||
if pricing_rule.round_free_qty:
|
||||
qty = round(qty)
|
||||
|
||||
free_item_data_args = {
|
||||
"item_code": free_item,
|
||||
|
@ -9,6 +9,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
refresh: function(frm){
|
||||
if(!frm.doc.__islocal) {
|
||||
frm.add_custom_button(__('Send Emails'), function(){
|
||||
if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."))
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
||||
args: {
|
||||
@ -25,7 +26,8 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
});
|
||||
});
|
||||
frm.add_custom_button(__('Download'), function(){
|
||||
var url = frappe.urllib.get_full_url(
|
||||
if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."))
|
||||
let url = frappe.urllib.get_full_url(
|
||||
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
||||
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
||||
$.ajax({
|
||||
|
@ -27,6 +27,7 @@
|
||||
"customers",
|
||||
"preferences",
|
||||
"orientation",
|
||||
"include_break",
|
||||
"include_ageing",
|
||||
"ageing_based_on",
|
||||
"section_break_14",
|
||||
@ -284,10 +285,16 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Terms and Conditions",
|
||||
"options": "Terms and Conditions"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "include_break",
|
||||
"fieldtype": "Check",
|
||||
"label": "Page Break After Each SoA"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-09-06 21:00:45.732505",
|
||||
"modified": "2022-10-17 17:47:08.662475",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
@ -321,5 +328,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -6,6 +6,7 @@ import copy
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_days, add_months, format_date, getdate, today
|
||||
from frappe.utils.jinja import validate_template
|
||||
@ -128,7 +129,8 @@ def get_report_pdf(doc, consolidated=True):
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
elif consolidated:
|
||||
result = "".join(list(statement_dict.values()))
|
||||
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
|
||||
result = delimiter.join(list(statement_dict.values()))
|
||||
return get_pdf(result, {"orientation": doc.orientation})
|
||||
else:
|
||||
for customer, statement_html in statement_dict.items():
|
||||
@ -240,8 +242,6 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
if int(primary_mandatory):
|
||||
if primary_email == "":
|
||||
continue
|
||||
elif (billing_email == "") and (primary_email == ""):
|
||||
continue
|
||||
|
||||
customer_list.append(
|
||||
{"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
|
||||
@ -273,8 +273,12 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
|
||||
link.link_doctype='Customer'
|
||||
and link.link_name=%s
|
||||
and contact.is_billing_contact=1
|
||||
{mcond}
|
||||
ORDER BY
|
||||
contact.creation desc""",
|
||||
contact.creation desc
|
||||
""".format(
|
||||
mcond=get_match_cond("Contact")
|
||||
),
|
||||
customer_name,
|
||||
)
|
||||
|
||||
@ -313,6 +317,8 @@ def send_emails(document_name, from_scheduler=False):
|
||||
attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}]
|
||||
|
||||
recipients, cc = get_recipients_and_cc(customer, doc)
|
||||
if not recipients:
|
||||
continue
|
||||
context = get_context(customer, doc)
|
||||
subject = frappe.render_template(doc.subject, context)
|
||||
message = frappe.render_template(doc.body, context)
|
||||
|
@ -569,6 +569,10 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
|
||||
if (frm.is_new()) {
|
||||
frm.clear_table("tax_withheld_vouchers");
|
||||
}
|
||||
},
|
||||
|
||||
is_subcontracted: function(frm) {
|
||||
|
@ -57,6 +57,8 @@
|
||||
"column_break_28",
|
||||
"total",
|
||||
"net_total",
|
||||
"tax_withholding_net_total",
|
||||
"base_tax_withholding_net_total",
|
||||
"taxes_section",
|
||||
"taxes_and_charges",
|
||||
"column_break_58",
|
||||
@ -89,7 +91,6 @@
|
||||
"section_break_44",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
"additional_discount_account",
|
||||
"column_break_46",
|
||||
"additional_discount_percentage",
|
||||
"discount_amount",
|
||||
@ -1421,6 +1422,26 @@
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Base Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible_depends_on": "tax_withheld_vouchers",
|
||||
"fieldname": "tax_withheld_vouchers_section",
|
||||
@ -1519,7 +1540,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-11 13:04:44.304389",
|
||||
"modified": "2022-11-04 01:02:44.544878",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -71,6 +71,9 @@ class PurchaseInvoice(BuyingController):
|
||||
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
self.set_onload("supplier_tds", supplier_tds)
|
||||
|
||||
if self.is_new():
|
||||
self.set("tax_withheld_vouchers", [])
|
||||
|
||||
def before_save(self):
|
||||
if not self.on_hold:
|
||||
self.release_date = ""
|
||||
@ -1415,7 +1418,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"Stock Ledger Entry",
|
||||
"Repost Item Valuation",
|
||||
"Payment Ledger Entry",
|
||||
"Purchase Invoice",
|
||||
"Tax Withheld Vouchers",
|
||||
)
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
"discount_amount",
|
||||
"base_rate_with_margin",
|
||||
"sec_break2",
|
||||
"apply_tds",
|
||||
"rate",
|
||||
"amount",
|
||||
"item_tax_template",
|
||||
@ -868,6 +869,12 @@
|
||||
"label": "Product Bundle",
|
||||
"options": "Product Bundle",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
|
@ -965,7 +965,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
pos_return.insert()
|
||||
pos_return.submit()
|
||||
|
||||
self.assertEqual(pos_return.get("payments")[0].amount, -1000)
|
||||
self.assertEqual(pos_return.get("payments")[0].amount, -500)
|
||||
self.assertEqual(pos_return.get("payments")[1].amount, -500)
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
make_pos_profile(
|
||||
|
@ -8,6 +8,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"barcode",
|
||||
"has_item_scanned",
|
||||
"item_code",
|
||||
"col_break1",
|
||||
"item_name",
|
||||
@ -876,12 +877,20 @@
|
||||
"label": "Purchase Order Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "barcode",
|
||||
"fieldname": "has_item_scanned",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-26 11:38:36.119339",
|
||||
"modified": "2022-11-02 12:53:12.693217",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -61,6 +61,9 @@ def get_party_details(inv):
|
||||
|
||||
|
||||
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
if inv.doctype == "Payment Entry":
|
||||
inv.tax_withholding_net_total = inv.net_total
|
||||
|
||||
pan_no = ""
|
||||
parties = []
|
||||
party_type, party = get_party_details(inv)
|
||||
@ -242,7 +245,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
if party_type == "Supplier":
|
||||
ldc = get_lower_deduction_certificate(tax_details, pan_no)
|
||||
if tax_deducted:
|
||||
net_total = inv.net_total
|
||||
net_total = inv.tax_withholding_net_total
|
||||
if ldc:
|
||||
tax_amount = get_tds_amount_from_ldc(
|
||||
ldc, parties, pan_no, tax_details, posting_date, net_total
|
||||
@ -272,6 +275,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
|
||||
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
|
||||
field = (
|
||||
"base_tax_withholding_net_total as base_net_total"
|
||||
if party_type == "Supplier"
|
||||
else "base_net_total"
|
||||
)
|
||||
voucher_wise_amount = {}
|
||||
vouchers = []
|
||||
|
||||
@ -288,7 +296,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
|
||||
)
|
||||
|
||||
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"])
|
||||
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field])
|
||||
|
||||
for d in invoices_details:
|
||||
vouchers.append(d.name)
|
||||
@ -392,7 +400,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
tds_amount = 0
|
||||
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
|
||||
|
||||
field = "sum(net_total)"
|
||||
field = "sum(tax_withholding_net_total)"
|
||||
|
||||
if cint(tax_details.consider_party_ledger_amount):
|
||||
invoice_filters.pop("apply_tds", None)
|
||||
@ -415,12 +423,12 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
)
|
||||
|
||||
supp_credit_amt += supp_jv_credit_amt
|
||||
supp_credit_amt += inv.net_total
|
||||
supp_credit_amt += inv.tax_withholding_net_total
|
||||
|
||||
threshold = tax_details.get("threshold", 0)
|
||||
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
|
||||
|
||||
if (threshold and inv.net_total >= threshold) or (
|
||||
if (threshold and inv.tax_withholding_net_total >= threshold) or (
|
||||
cumulative_threshold and supp_credit_amt >= cumulative_threshold
|
||||
):
|
||||
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
|
||||
@ -428,11 +436,11 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
):
|
||||
# Get net total again as TDS is calculated on net total
|
||||
# Grand is used to just check for threshold breach
|
||||
net_total = 0
|
||||
if vouchers:
|
||||
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)")
|
||||
|
||||
net_total += inv.net_total
|
||||
net_total = (
|
||||
frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)")
|
||||
or 0.0
|
||||
)
|
||||
net_total += inv.tax_withholding_net_total
|
||||
supp_credit_amt = net_total - cumulative_threshold
|
||||
|
||||
if ldc and is_valid_certificate(
|
||||
@ -440,7 +448,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
ldc.valid_upto,
|
||||
inv.get("posting_date") or inv.get("transaction_date"),
|
||||
tax_deducted,
|
||||
inv.net_total,
|
||||
inv.tax_withholding_net_total,
|
||||
ldc.certificate_limit,
|
||||
):
|
||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
@ -523,7 +531,7 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
|
||||
limit_consumed = frappe.db.get_value(
|
||||
"Purchase Invoice",
|
||||
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
|
||||
"sum(net_total)",
|
||||
"sum(tax_withholding_net_total)",
|
||||
)
|
||||
|
||||
if is_valid_certificate(
|
||||
|
@ -186,6 +186,46 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_tds_calculation_on_net_total_partial_tds(self):
|
||||
frappe.db.set_value(
|
||||
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
|
||||
)
|
||||
invoices = []
|
||||
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier4", rate=20000, do_not_save=True)
|
||||
pi.extend(
|
||||
"items",
|
||||
[
|
||||
{
|
||||
"doctype": "Purchase Invoice Item",
|
||||
"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
|
||||
"qty": 1,
|
||||
"rate": 20000,
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Stock Received But Not Billed - _TC",
|
||||
"apply_tds": 0,
|
||||
},
|
||||
{
|
||||
"doctype": "Purchase Invoice Item",
|
||||
"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
|
||||
"qty": 1,
|
||||
"rate": 35000,
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Stock Received But Not Billed - _TC",
|
||||
"apply_tds": 1,
|
||||
},
|
||||
],
|
||||
)
|
||||
pi.save()
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
self.assertEqual(pi.taxes[0].tax_amount, 5500)
|
||||
|
||||
# cancel invoices to avoid clashing
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_multi_category_single_supplier(self):
|
||||
frappe.db.set_value(
|
||||
"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"
|
||||
|
@ -128,6 +128,12 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
|
||||
new_gl_map = []
|
||||
for d in gl_map:
|
||||
cost_center = d.get("cost_center")
|
||||
|
||||
# Validate budget against main cost center
|
||||
validate_expense_against_budget(
|
||||
d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
|
||||
)
|
||||
|
||||
if cost_center and cost_center_allocation.get(cost_center):
|
||||
for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
|
||||
gle = copy.deepcopy(d)
|
||||
|
@ -1009,7 +1009,7 @@ class ReceivablePayableReport(object):
|
||||
"{range3}-{range4}".format(
|
||||
range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"]
|
||||
),
|
||||
"{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")),
|
||||
_("{range4}-Above").format(range4=cint(self.filters["range4"]) + 1),
|
||||
]
|
||||
):
|
||||
self.add_column(label=label, fieldname="range" + str(i + 1))
|
||||
|
@ -75,7 +75,7 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (column.fieldname.includes('variance')) {
|
||||
if (column.fieldname.includes(__("variance"))) {
|
||||
|
||||
if (data[column.fieldname] < 0) {
|
||||
value = "<span style='color:red'>" + value + "</span>";
|
||||
|
@ -383,8 +383,8 @@ def get_chart_data(filters, columns, data):
|
||||
"data": {
|
||||
"labels": labels,
|
||||
"datasets": [
|
||||
{"name": "Budget", "chartType": "bar", "values": budget_values},
|
||||
{"name": "Actual Expense", "chartType": "bar", "values": actual_values},
|
||||
{"name": _("Budget"), "chartType": "bar", "values": budget_values},
|
||||
{"name": _("Actual Expense"), "chartType": "bar", "values": actual_values},
|
||||
],
|
||||
},
|
||||
"type": "bar",
|
||||
|
@ -396,7 +396,7 @@ class Deferred_Revenue_and_Expense_Report(object):
|
||||
"labels": [period.label for period in self.period_list],
|
||||
"datasets": [
|
||||
{
|
||||
"name": "Actual Posting",
|
||||
"name": _("Actual Posting"),
|
||||
"chartType": "bar",
|
||||
"values": [x.actual for x in self.period_total],
|
||||
}
|
||||
@ -410,7 +410,7 @@ class Deferred_Revenue_and_Expense_Report(object):
|
||||
|
||||
if self.filters.with_upcoming_postings:
|
||||
chart["data"]["datasets"].append(
|
||||
{"name": "Expected", "chartType": "line", "values": [x.total for x in self.period_total]}
|
||||
{"name": _("Expected"), "chartType": "line", "values": [x.total for x in self.period_total]}
|
||||
)
|
||||
|
||||
return chart
|
||||
|
@ -432,7 +432,11 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
set_values_from_purchase_doc: function(frm, doctype, purchase_doc) {
|
||||
frm.set_value('company', purchase_doc.company);
|
||||
frm.set_value('purchase_date', purchase_doc.posting_date);
|
||||
if (purchase_doc.bill_date) {
|
||||
frm.set_value('purchase_date', purchase_doc.bill_date);
|
||||
} else {
|
||||
frm.set_value('purchase_date', purchase_doc.posting_date);
|
||||
}
|
||||
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
|
||||
if (!item) {
|
||||
doctype_field = frappe.scrub(doctype)
|
||||
|
@ -221,7 +221,7 @@ class TestAsset(AssetSetup):
|
||||
asset.precision("gross_purchase_amount"),
|
||||
)
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
|
||||
@ -283,7 +283,7 @@ class TestAsset(AssetSetup):
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
|
||||
|
@ -135,6 +135,7 @@ class AssetRepair(AccountsController):
|
||||
"basic_rate": stock_item.valuation_rate,
|
||||
"serial_no": stock_item.serial_no,
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -101,6 +101,11 @@ frappe.ui.form.on("Purchase Order", {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
|
||||
// On cancel and amending a purchase order with advance payment, reset advance paid amount
|
||||
if (frm.is_new()) {
|
||||
frm.set_value("advance_paid", 0)
|
||||
}
|
||||
},
|
||||
|
||||
apply_tds: function(frm) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate
|
||||
from frappe.utils.data import today
|
||||
|
||||
@ -709,13 +709,10 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
pi.insert()
|
||||
self.assertTrue(pi.get("payment_schedule"))
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
|
||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 1
|
||||
)
|
||||
|
||||
po_doc = create_purchase_order()
|
||||
|
||||
pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC")
|
||||
@ -735,9 +732,31 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe.name)
|
||||
pe_doc.cancel()
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0
|
||||
)
|
||||
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
|
||||
def test_advance_paid_upon_payment_entry_cancellation(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
po_doc = create_purchase_order()
|
||||
|
||||
pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_from_account_currency = po_doc.currency
|
||||
pe.paid_to_account_currency = po_doc.currency
|
||||
pe.source_exchange_rate = 1
|
||||
pe.target_exchange_rate = 1
|
||||
pe.paid_amount = po_doc.grand_total
|
||||
pe.save(ignore_permissions=True)
|
||||
pe.submit()
|
||||
|
||||
po_doc.reload()
|
||||
self.assertEqual(po_doc.advance_paid, po_doc.base_grand_total)
|
||||
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe.name)
|
||||
pe_doc.cancel()
|
||||
|
||||
po_doc.reload()
|
||||
self.assertEqual(po_doc.advance_paid, 0)
|
||||
|
||||
def test_schedule_date(self):
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
|
@ -156,6 +156,8 @@ class TestSupplier(FrappeTestCase):
|
||||
def test_serach_fields_for_supplier(self):
|
||||
from erpnext.controllers.queries import supplier_query
|
||||
|
||||
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Naming Series")
|
||||
|
||||
supplier_name = create_supplier(supplier_name="Test Supplier 1").name
|
||||
|
||||
make_property_setter(
|
||||
@ -187,6 +189,8 @@ class TestSupplier(FrappeTestCase):
|
||||
self.assertEqual(data[0].supplier_type, "Company")
|
||||
self.assertTrue("supplier_type" in data[0])
|
||||
|
||||
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Supplier Name")
|
||||
|
||||
|
||||
def create_supplier(**args):
|
||||
args = frappe._dict(args)
|
||||
|
@ -7,7 +7,7 @@ import json
|
||||
import frappe
|
||||
from frappe import _, throw
|
||||
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
@ -804,15 +804,12 @@ class AccountsController(TransactionBase):
|
||||
self.set("advances", [])
|
||||
advance_allocated = 0
|
||||
for d in res:
|
||||
if d.against_order:
|
||||
allocated_amount = flt(d.amount)
|
||||
if self.get("party_account_currency") == self.company_currency:
|
||||
amount = self.get("base_rounded_total") or self.base_grand_total
|
||||
else:
|
||||
if self.get("party_account_currency") == self.company_currency:
|
||||
amount = self.get("base_rounded_total") or self.base_grand_total
|
||||
else:
|
||||
amount = self.get("rounded_total") or self.grand_total
|
||||
amount = self.get("rounded_total") or self.grand_total
|
||||
|
||||
allocated_amount = min(amount - advance_allocated, d.amount)
|
||||
allocated_amount = min(amount - advance_allocated, d.amount)
|
||||
advance_allocated += flt(allocated_amount)
|
||||
|
||||
advance_row = {
|
||||
@ -1334,30 +1331,20 @@ class AccountsController(TransactionBase):
|
||||
return stock_items
|
||||
|
||||
def set_total_advance_paid(self):
|
||||
if self.doctype == "Sales Order":
|
||||
dr_or_cr = "credit_in_account_currency"
|
||||
rev_dr_or_cr = "debit_in_account_currency"
|
||||
party = self.customer
|
||||
else:
|
||||
dr_or_cr = "debit_in_account_currency"
|
||||
rev_dr_or_cr = "credit_in_account_currency"
|
||||
party = self.supplier
|
||||
|
||||
advance = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
against_voucher_type = %s and against_voucher = %s and party=%s
|
||||
and docstatus = 1
|
||||
""".format(
|
||||
dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr
|
||||
),
|
||||
(self.doctype, self.name, party),
|
||||
as_dict=1,
|
||||
) # nosec
|
||||
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||
party = self.customer if self.doctype == "Sales Order" else self.supplier
|
||||
advance = (
|
||||
frappe.qb.from_(ple)
|
||||
.select(ple.account_currency, Abs(Sum(ple.amount)).as_("amount"))
|
||||
.where(
|
||||
(ple.against_voucher_type == self.doctype)
|
||||
& (ple.against_voucher_no == self.name)
|
||||
& (ple.party == party)
|
||||
& (ple.delinked == 0)
|
||||
& (ple.company == self.company)
|
||||
)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
if advance:
|
||||
advance = advance[0]
|
||||
|
@ -85,7 +85,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=
|
||||
|
||||
fields = ["name"]
|
||||
if cust_master_name != "Customer Name":
|
||||
fields = ["customer_name"]
|
||||
fields.append("customer_name")
|
||||
|
||||
fields = get_fields(doctype, fields)
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
@ -123,7 +123,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=
|
||||
|
||||
fields = ["name"]
|
||||
if supp_master_name != "Supplier Name":
|
||||
fields = ["supplier_name"]
|
||||
fields.append("supplier_name")
|
||||
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
|
@ -58,12 +58,25 @@ class calculate_taxes_and_totals(object):
|
||||
self.initialize_taxes()
|
||||
self.determine_exclusive_rate()
|
||||
self.calculate_net_total()
|
||||
self.calculate_tax_withholding_net_total()
|
||||
self.calculate_taxes()
|
||||
self.manipulate_grand_total_for_inclusive_tax()
|
||||
self.calculate_totals()
|
||||
self._cleanup()
|
||||
self.calculate_total_net_weight()
|
||||
|
||||
def calculate_tax_withholding_net_total(self):
|
||||
if hasattr(self.doc, "tax_withholding_net_total"):
|
||||
sum_net_amount = 0
|
||||
sum_base_net_amount = 0
|
||||
for item in self.doc.get("items"):
|
||||
if hasattr(item, "apply_tds") and item.apply_tds:
|
||||
sum_net_amount += item.net_amount
|
||||
sum_base_net_amount += item.base_net_amount
|
||||
|
||||
self.doc.tax_withholding_net_total = sum_net_amount
|
||||
self.doc.base_tax_withholding_net_total = sum_base_net_amount
|
||||
|
||||
def validate_item_tax_template(self):
|
||||
for item in self.doc.get("items"):
|
||||
if item.item_code and item.get("item_tax_template"):
|
||||
@ -889,24 +902,33 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
|
||||
|
||||
def set_total_amount_to_default_mop(self, total_amount_to_pay):
|
||||
default_mode_of_payment = frappe.db.get_value(
|
||||
"POS Payment Method",
|
||||
{"parent": self.doc.pos_profile, "default": 1},
|
||||
["mode_of_payment"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.payments = []
|
||||
self.doc.append(
|
||||
"payments",
|
||||
{
|
||||
"mode_of_payment": default_mode_of_payment.mode_of_payment,
|
||||
"amount": total_amount_to_pay,
|
||||
"default": 1,
|
||||
},
|
||||
total_paid_amount = 0
|
||||
for payment in self.doc.get("payments"):
|
||||
total_paid_amount += (
|
||||
payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount
|
||||
)
|
||||
|
||||
pending_amount = total_amount_to_pay - total_paid_amount
|
||||
|
||||
if pending_amount > 0:
|
||||
default_mode_of_payment = frappe.db.get_value(
|
||||
"POS Payment Method",
|
||||
{"parent": self.doc.pos_profile, "default": 1},
|
||||
["mode_of_payment"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.payments = []
|
||||
self.doc.append(
|
||||
"payments",
|
||||
{
|
||||
"mode_of_payment": default_mode_of_payment.mode_of_payment,
|
||||
"amount": pending_amount,
|
||||
"default": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_itemised_tax_breakup_html(doc):
|
||||
if not doc.taxes:
|
||||
|
@ -317,3 +317,4 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
||||
erpnext.patches.v13_0.update_schedule_type_in_loans
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
||||
erpnext.patches.v14_0.update_tds_fields
|
||||
|
29
erpnext/patches/v14_0/update_tds_fields.py
Normal file
29
erpnext/patches/v14_0/update_tds_fields.py
Normal file
@ -0,0 +1,29 @@
|
||||
import frappe
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
|
||||
|
||||
|
||||
def execute():
|
||||
# Only do for current fiscal year, no need to repost for all years
|
||||
for company in frappe.get_all("Company"):
|
||||
try:
|
||||
fiscal_year_details = get_fiscal_year(date=nowdate(), company=company.name, as_dict=True)
|
||||
|
||||
purchase_invoice = frappe.qb.DocType("Purchase Invoice")
|
||||
|
||||
frappe.qb.update(purchase_invoice).set(
|
||||
purchase_invoice.tax_withholding_net_total, purchase_invoice.net_total
|
||||
).set(
|
||||
purchase_invoice.base_tax_withholding_net_total, purchase_invoice.base_net_total
|
||||
).where(
|
||||
purchase_invoice.company == company.name
|
||||
).where(
|
||||
purchase_invoice.apply_tds == 1
|
||||
).where(
|
||||
purchase_invoice.posting_date >= fiscal_year_details.year_start_date
|
||||
).where(
|
||||
purchase_invoice.docstatus == 1
|
||||
).run()
|
||||
except FiscalYearError:
|
||||
pass
|
@ -30,28 +30,28 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
||||
get_dt_columns() {
|
||||
this.columns = [
|
||||
{
|
||||
name: "Date",
|
||||
name: __("Date"),
|
||||
editable: false,
|
||||
width: 100,
|
||||
},
|
||||
|
||||
{
|
||||
name: "Party Type",
|
||||
name: __("Party Type"),
|
||||
editable: false,
|
||||
width: 95,
|
||||
},
|
||||
{
|
||||
name: "Party",
|
||||
name: __("Party"),
|
||||
editable: false,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: "Description",
|
||||
name: __("Description"),
|
||||
editable: false,
|
||||
width: 350,
|
||||
},
|
||||
{
|
||||
name: "Deposit",
|
||||
name: __("Deposit"),
|
||||
editable: false,
|
||||
width: 100,
|
||||
format: (value) =>
|
||||
@ -60,7 +60,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
||||
"</span>",
|
||||
},
|
||||
{
|
||||
name: "Withdrawal",
|
||||
name: __("Withdrawal"),
|
||||
editable: false,
|
||||
width: 100,
|
||||
format: (value) =>
|
||||
@ -69,26 +69,26 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
||||
"</span>",
|
||||
},
|
||||
{
|
||||
name: "Unallocated Amount",
|
||||
name: __("Unallocated Amount"),
|
||||
editable: false,
|
||||
width: 100,
|
||||
format: (value) =>
|
||||
"<span style='color:blue;'>" +
|
||||
"<span style='color:var(--blue-500);'>" +
|
||||
format_currency(value, this.currency) +
|
||||
"</span>",
|
||||
},
|
||||
{
|
||||
name: "Reference Number",
|
||||
name: __("Reference Number"),
|
||||
editable: false,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
name: "Actions",
|
||||
name: __("Actions"),
|
||||
editable: false,
|
||||
sortable: false,
|
||||
focusable: false,
|
||||
dropdown: false,
|
||||
width: 80,
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -118,7 +118,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
||||
row["reference_number"],
|
||||
`
|
||||
<Button class="btn btn-primary btn-xs center" data-name = ${row["name"]} >
|
||||
Actions
|
||||
${__("Actions")}
|
||||
</a>
|
||||
`,
|
||||
];
|
||||
|
@ -87,33 +87,33 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
get_dt_columns() {
|
||||
this.columns = [
|
||||
{
|
||||
name: "Document Type",
|
||||
name: __("Document Type"),
|
||||
editable: false,
|
||||
width: 125,
|
||||
},
|
||||
{
|
||||
name: "Document Name",
|
||||
name: __("Document Name"),
|
||||
editable: false,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: "Reference Date",
|
||||
name: __("Reference Date"),
|
||||
editable: false,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
name: "Amount",
|
||||
name: __("Amount"),
|
||||
editable: false,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: "Party",
|
||||
name: __("Party"),
|
||||
editable: false,
|
||||
width: 120,
|
||||
},
|
||||
|
||||
{
|
||||
name: "Reference Number",
|
||||
name: __("Reference Number"),
|
||||
editable: false,
|
||||
width: 140,
|
||||
},
|
||||
@ -222,7 +222,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "no_matching_vouchers",
|
||||
options: "<div class=\"text-muted text-center\">No Matching Vouchers Found</div>"
|
||||
options: __("<div class=\"text-muted text-center\">{0}</div>", [__("No Matching Vouchers Found")])
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
@ -444,10 +444,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
vouchers: vouchers,
|
||||
},
|
||||
callback: (response) => {
|
||||
const alert_string =
|
||||
"Bank Transaction " +
|
||||
this.bank_transaction.name +
|
||||
" Matched";
|
||||
const alert_string = __("Bank Transaction {0} Matched", [this.bank_transaction.name]);
|
||||
frappe.show_alert(alert_string);
|
||||
this.update_dt_cards(response.message);
|
||||
this.dialog.hide();
|
||||
@ -471,10 +468,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
cost_center: values.cost_center,
|
||||
},
|
||||
callback: (response) => {
|
||||
const alert_string =
|
||||
"Bank Transaction " +
|
||||
this.bank_transaction.name +
|
||||
" added as Payment Entry";
|
||||
const alert_string = __("Bank Transaction {0} added as Payment Entry", [this.bank_transaction.name]);
|
||||
frappe.show_alert(alert_string);
|
||||
this.update_dt_cards(response.message);
|
||||
this.dialog.hide();
|
||||
@ -498,10 +492,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
second_account: values.second_account,
|
||||
},
|
||||
callback: (response) => {
|
||||
const alert_string =
|
||||
"Bank Transaction " +
|
||||
this.bank_transaction.name +
|
||||
" added as Journal Entry";
|
||||
const alert_string = __("Bank Transaction {0} added as Journal Entry", [this.bank_transaction.name]);
|
||||
frappe.show_alert(alert_string);
|
||||
this.update_dt_cards(response.message);
|
||||
this.dialog.hide();
|
||||
@ -520,10 +511,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
party: values.party,
|
||||
},
|
||||
callback: (response) => {
|
||||
const alert_string =
|
||||
"Bank Transaction " +
|
||||
this.bank_transaction.name +
|
||||
" updated";
|
||||
const alert_string = __("Bank Transaction {0} updated", [this.bank_transaction.name]);
|
||||
frappe.show_alert(alert_string);
|
||||
this.update_dt_cards(response.message);
|
||||
this.dialog.hide();
|
||||
|
@ -15,20 +15,20 @@ erpnext.accounts.bank_reconciliation.NumberCardManager = class NumberCardManager
|
||||
var chart_data = [
|
||||
{
|
||||
value: this.bank_statement_closing_balance,
|
||||
label: "Closing Balance as per Bank Statement",
|
||||
label: __("Closing Balance as per Bank Statement"),
|
||||
datatype: "Currency",
|
||||
currency: this.currency,
|
||||
},
|
||||
{
|
||||
value: this.cleared_balance,
|
||||
label: "Closing Balance as per ERP",
|
||||
label: __("Closing Balance as per ERP"),
|
||||
datatype: "Currency",
|
||||
currency: this.currency,
|
||||
},
|
||||
{
|
||||
value:
|
||||
this.bank_statement_closing_balance - this.cleared_balance,
|
||||
label: "Difference",
|
||||
label: __("Difference"),
|
||||
datatype: "Currency",
|
||||
currency: this.currency,
|
||||
},
|
||||
|
@ -341,6 +341,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.set_dynamic_labels();
|
||||
this.setup_sms();
|
||||
this.setup_quality_inspection();
|
||||
this.validate_has_items();
|
||||
}
|
||||
|
||||
scan_barcode() {
|
||||
@ -348,6 +349,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
barcode_scanner.process_scan();
|
||||
}
|
||||
|
||||
validate_has_items () {
|
||||
let table = this.frm.doc.items;
|
||||
this.frm.has_items = (table && table.length
|
||||
&& table[0].qty && table[0].item_code);
|
||||
}
|
||||
|
||||
apply_default_taxes() {
|
||||
var me = this;
|
||||
var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
|
||||
@ -1200,7 +1207,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
"base_rounding_adjustment"], company_currency);
|
||||
|
||||
this.frm.set_currency_labels(["total", "net_total", "total_taxes_and_charges", "discount_amount",
|
||||
"grand_total", "taxes_and_charges_added", "taxes_and_charges_deducted",
|
||||
"grand_total", "taxes_and_charges_added", "taxes_and_charges_deducted","tax_withholding_net_total",
|
||||
"rounded_total", "in_words", "paid_amount", "write_off_amount", "operating_cost",
|
||||
"scrap_material_cost", "rounding_adjustment", "raw_material_cost",
|
||||
"total_cost"], this.frm.doc.currency);
|
||||
@ -1217,7 +1224,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
// toggle fields
|
||||
this.frm.toggle_display(["conversion_rate", "base_total", "base_net_total",
|
||||
this.frm.toggle_display(["conversion_rate", "base_total", "base_net_total", "base_tax_withholding_net_total",
|
||||
"base_total_taxes_and_charges", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted",
|
||||
"base_grand_total", "base_rounded_total", "base_in_words", "base_discount_amount",
|
||||
"base_paid_amount", "base_write_off_amount", "base_operating_cost", "base_raw_material_cost",
|
||||
@ -1404,7 +1411,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
if (!r.exc && r.message) {
|
||||
me._set_values_for_item_list(r.message);
|
||||
if(item) me.set_gross_profit(item);
|
||||
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
|
||||
if (me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1577,6 +1584,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
for (let key in pr_row) {
|
||||
row_to_modify[key] = pr_row[key];
|
||||
}
|
||||
this.frm.script_manager.copy_from_first_row("items", row_to_modify, ["expense_account", "income_account"]);
|
||||
});
|
||||
|
||||
// free_item_data is a temporary variable
|
||||
|
@ -47,42 +47,49 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
return;
|
||||
}
|
||||
|
||||
frappe
|
||||
.call({
|
||||
method: this.scan_api,
|
||||
args: {
|
||||
search_value: input,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
const data = r && r.message;
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
this.show_alert(__("Cannot find Item with this Barcode"), "red");
|
||||
this.clean_up();
|
||||
this.play_fail_sound();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
this.scan_api_call(input, (r) => {
|
||||
const data = r && r.message;
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
this.show_alert(__("Cannot find Item with this Barcode"), "red");
|
||||
this.clean_up();
|
||||
this.play_fail_sound();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
me.update_table(data).then(row => {
|
||||
this.play_success_sound();
|
||||
resolve(row);
|
||||
}).catch(() => {
|
||||
this.play_fail_sound();
|
||||
reject();
|
||||
});
|
||||
me.update_table(data).then(row => {
|
||||
this.play_success_sound();
|
||||
resolve(row);
|
||||
}).catch(() => {
|
||||
this.play_fail_sound();
|
||||
reject();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
scan_api_call(input, callback) {
|
||||
frappe
|
||||
.call({
|
||||
method: this.scan_api,
|
||||
args: {
|
||||
search_value: input,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
callback(r);
|
||||
});
|
||||
}
|
||||
|
||||
update_table(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||
|
||||
const {item_code, barcode, batch_no, serial_no, uom} = data;
|
||||
|
||||
let row = this.get_row_to_modify_on_scan(item_code, batch_no, uom);
|
||||
let row = this.get_row_to_modify_on_scan(item_code, batch_no, uom, barcode);
|
||||
|
||||
this.is_new_row = false;
|
||||
if (!row) {
|
||||
if (this.dont_allow_new_row) {
|
||||
this.show_alert(__("Maximum quantity scanned for item {0}.", [item_code]), "red");
|
||||
@ -90,11 +97,13 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
this.is_new_row = true;
|
||||
|
||||
// add new row if new item/batch is scanned
|
||||
row = frappe.model.add_child(this.frm.doc, cur_grid.doctype, this.items_table_name);
|
||||
// trigger any row add triggers defined on child table.
|
||||
this.frm.script_manager.trigger(`${this.items_table_name}_add`, row.doctype, row.name);
|
||||
this.frm.has_items = false;
|
||||
}
|
||||
|
||||
if (this.is_duplicate_serial_no(row, serial_no)) {
|
||||
@ -105,7 +114,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.set_selector_trigger_flag(data),
|
||||
() => this.set_item(row, item_code).then(qty => {
|
||||
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
|
||||
this.show_scan_message(row.idx, row.item_code, qty);
|
||||
}),
|
||||
() => this.set_barcode_uom(row, uom),
|
||||
@ -136,7 +145,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
frappe.flags.hide_serial_batch_dialog = false;
|
||||
}
|
||||
|
||||
set_item(row, item_code) {
|
||||
set_item(row, item_code, barcode, batch_no, serial_no) {
|
||||
return new Promise(resolve => {
|
||||
const increment = async (value = 1) => {
|
||||
const item_data = {item_code: item_code};
|
||||
@ -149,12 +158,186 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
|
||||
increment(value).then((value) => resolve(value));
|
||||
});
|
||||
} else if (this.frm.has_items) {
|
||||
this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
|
||||
} else {
|
||||
increment().then((value) => resolve(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no) {
|
||||
var me = this;
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __("Scan barcode for item {0}", [item_code]),
|
||||
fields: me.get_fields_for_dialog(row, item_code, barcode, batch_no, serial_no),
|
||||
})
|
||||
|
||||
this.dialog.set_primary_action(__("Update"), () => {
|
||||
const item_data = {item_code: item_code};
|
||||
item_data[this.qty_field] = this.dialog.get_value("scanned_qty");
|
||||
item_data["has_item_scanned"] = 1;
|
||||
|
||||
this.remaining_qty = flt(this.dialog.get_value("qty")) - flt(this.dialog.get_value("scanned_qty"));
|
||||
frappe.model.set_value(row.doctype, row.name, item_data);
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.set_batch_no(row, this.dialog.get_value("batch_no")),
|
||||
() => this.set_barcode(row, this.dialog.get_value("barcode")),
|
||||
() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
|
||||
() => this.add_child_for_remaining_qty(row),
|
||||
() => this.clean_up()
|
||||
]);
|
||||
|
||||
this.dialog.hide();
|
||||
});
|
||||
|
||||
this.dialog.show();
|
||||
|
||||
this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
|
||||
this.$scan_btn.css("display", "inline");
|
||||
}
|
||||
|
||||
get_fields_for_dialog(row, item_code, barcode, batch_no, serial_no) {
|
||||
let fields = [
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "barcode_scanner",
|
||||
options: "Barcode",
|
||||
label: __("Scan Barcode"),
|
||||
onchange: (e) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.value) {
|
||||
this.scan_api_call(e.target.value, (r) => {
|
||||
if (r.message) {
|
||||
this.update_dialog_values(item_code, r);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
},
|
||||
{
|
||||
fieldtype: "Float",
|
||||
fieldname: "qty",
|
||||
label: __("Quantity to Scan"),
|
||||
default: row[this.qty_field] || 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
fieldname: "column_break_1",
|
||||
},
|
||||
{
|
||||
fieldtype: "Float",
|
||||
read_only: 1,
|
||||
fieldname: "scanned_qty",
|
||||
label: __("Scanned Quantity"),
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
}
|
||||
]
|
||||
|
||||
if (batch_no) {
|
||||
fields.push({
|
||||
fieldtype: "Link",
|
||||
fieldname: "batch_no",
|
||||
options: "Batch No",
|
||||
label: __("Batch No"),
|
||||
default: batch_no,
|
||||
read_only: 1,
|
||||
hidden: 1
|
||||
});
|
||||
}
|
||||
|
||||
if (serial_no) {
|
||||
fields.push({
|
||||
fieldtype: "Small Text",
|
||||
fieldname: "serial_no",
|
||||
label: __("Serial Nos"),
|
||||
default: serial_no,
|
||||
read_only: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (barcode) {
|
||||
fields.push({
|
||||
fieldtype: "Data",
|
||||
fieldname: "barcode",
|
||||
options: "Barcode",
|
||||
label: __("Barcode"),
|
||||
default: barcode,
|
||||
read_only: 1,
|
||||
hidden: 1
|
||||
});
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
update_dialog_values(scanned_item, r) {
|
||||
const {item_code, barcode, batch_no, serial_no} = r.message;
|
||||
|
||||
this.dialog.set_value("barcode_scanner", "");
|
||||
if (item_code === scanned_item &&
|
||||
(this.dialog.get_value("barcode") === barcode || batch_no || serial_no)) {
|
||||
|
||||
if (batch_no) {
|
||||
this.dialog.set_value("batch_no", batch_no);
|
||||
}
|
||||
|
||||
if (serial_no) {
|
||||
|
||||
this.validate_duplicate_serial_no(serial_no);
|
||||
let serial_nos = this.dialog.get_value("serial_no") + "\n" + serial_no;
|
||||
this.dialog.set_value("serial_no", serial_nos);
|
||||
}
|
||||
|
||||
let qty = flt(this.dialog.get_value("scanned_qty")) + 1.0;
|
||||
this.dialog.set_value("scanned_qty", qty);
|
||||
}
|
||||
}
|
||||
|
||||
validate_duplicate_serial_no(serial_no) {
|
||||
let serial_nos = this.dialog.get_value("serial_no") ?
|
||||
this.dialog.get_value("serial_no").split("\n") : [];
|
||||
|
||||
if (in_list(serial_nos, serial_no)) {
|
||||
frappe.throw(__("Serial No {0} already scanned", [serial_no]));
|
||||
}
|
||||
}
|
||||
|
||||
add_child_for_remaining_qty(prev_row) {
|
||||
if (this.remaining_qty && this.remaining_qty >0) {
|
||||
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||
let row = frappe.model.add_child(this.frm.doc, cur_grid.doctype, this.items_table_name);
|
||||
|
||||
let ignore_fields = ["name", "idx", "batch_no", "barcode",
|
||||
"received_qty", "serial_no", "has_item_scanned"];
|
||||
|
||||
for (let key in prev_row) {
|
||||
if (in_list(ignore_fields, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
row[key] = prev_row[key];
|
||||
}
|
||||
|
||||
row[this.qty_field] = this.remaining_qty;
|
||||
if (this.qty_field == "qty" && frappe.meta.has_field(row.doctype, "stock_qty")) {
|
||||
row["stock_qty"] = this.remaining_qty * row.conversion_factor;
|
||||
}
|
||||
|
||||
this.frm.script_manager.trigger("item_code", row.doctype, row.name);
|
||||
}
|
||||
}
|
||||
|
||||
async set_serial_no(row, serial_no) {
|
||||
if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
|
||||
const existing_serial_nos = row[this.serial_no_field];
|
||||
@ -205,7 +388,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
return is_duplicate;
|
||||
}
|
||||
|
||||
get_row_to_modify_on_scan(item_code, batch_no, uom) {
|
||||
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
|
||||
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||
|
||||
// Check if batch is scanned and table has batch no field
|
||||
@ -214,12 +397,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
|
||||
const matching_row = (row) => {
|
||||
const item_match = row.item_code == item_code;
|
||||
const batch_match = row[this.batch_no_field] == batch_no;
|
||||
const batch_match = (!row[this.batch_no_field] || row[this.batch_no_field] == batch_no);
|
||||
const uom_match = !uom || row[this.uom_field] == uom;
|
||||
const qty_in_limit = flt(row[this.qty_field]) < flt(row[this.max_qty_field]);
|
||||
const item_scanned = row.has_item_scanned;
|
||||
|
||||
return item_match
|
||||
&& uom_match
|
||||
&& !item_scanned
|
||||
&& (!is_batch_no_scan || batch_match)
|
||||
&& (!check_max_qty || qty_in_limit)
|
||||
}
|
||||
|
@ -345,6 +345,8 @@ class TestCustomer(FrappeTestCase):
|
||||
def test_serach_fields_for_customer(self):
|
||||
from erpnext.controllers.queries import customer_query
|
||||
|
||||
frappe.db.set_value("Selling Settings", None, "cust_master_name", "Naming Series")
|
||||
|
||||
make_property_setter(
|
||||
"Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype"
|
||||
)
|
||||
@ -369,6 +371,8 @@ class TestCustomer(FrappeTestCase):
|
||||
self.assertEqual(data[0].territory, "_Test Territory")
|
||||
self.assertTrue("territory" in data[0])
|
||||
|
||||
frappe.db.set_value("Selling Settings", None, "cust_master_name", "Customer Name")
|
||||
|
||||
|
||||
def get_customer_dict(customer_name):
|
||||
return {
|
||||
|
@ -124,6 +124,11 @@ frappe.ui.form.on("Sales Order", {
|
||||
return query;
|
||||
});
|
||||
|
||||
// On cancel and amending a sales order with advance payment, reset advance paid amount
|
||||
if (frm.is_new()) {
|
||||
frm.set_value("advance_paid", 0)
|
||||
}
|
||||
|
||||
frm.ignore_doctypes_on_cancel_all = ['Purchase Order'];
|
||||
},
|
||||
|
||||
|
@ -6,7 +6,7 @@ import json
|
||||
import frappe
|
||||
import frappe.permissions
|
||||
from frappe.core.doctype.user_permission.test_user_permission import create_user
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
|
||||
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
||||
@ -1346,6 +1346,33 @@ class TestSalesOrder(FrappeTestCase):
|
||||
|
||||
self.assertRaises(frappe.LinkExistsError, so_doc.cancel)
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
|
||||
def test_advance_paid_upon_payment_cancellation(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
so = make_sales_order()
|
||||
|
||||
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_from_account_currency = so.currency
|
||||
pe.paid_to_account_currency = so.currency
|
||||
pe.source_exchange_rate = 1
|
||||
pe.target_exchange_rate = 1
|
||||
pe.paid_amount = so.grand_total
|
||||
pe.save(ignore_permissions=True)
|
||||
pe.submit()
|
||||
so.reload()
|
||||
|
||||
self.assertEqual(so.advance_paid, so.base_grand_total)
|
||||
|
||||
# cancel advance payment
|
||||
pe.reload()
|
||||
pe.cancel()
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 0)
|
||||
|
||||
def test_cancel_sales_order_after_cancel_payment_entry(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
@ -1747,6 +1774,69 @@ class TestSalesOrder(FrappeTestCase):
|
||||
sales_order.save()
|
||||
self.assertEqual(sales_order.taxes[0].tax_amount, 0)
|
||||
|
||||
def test_sales_order_partial_advance_payment(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
|
||||
create_payment_entry,
|
||||
get_payment_entry,
|
||||
)
|
||||
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
|
||||
|
||||
# Make a customer
|
||||
customer = get_customer_dict("QA Logistics")
|
||||
frappe.get_doc(customer).insert()
|
||||
|
||||
# Make a Sales Order
|
||||
so = make_sales_order(
|
||||
customer="QA Logistics",
|
||||
item_list=[
|
||||
{"item_code": "_Test Item", "qty": 1, "rate": 200},
|
||||
{"item_code": "_Test Item 2", "qty": 1, "rate": 300},
|
||||
],
|
||||
)
|
||||
|
||||
# Create a advance payment against that Sales Order
|
||||
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_from_account_currency = so.currency
|
||||
pe.paid_to_account_currency = so.currency
|
||||
pe.source_exchange_rate = 1
|
||||
pe.target_exchange_rate = 1
|
||||
pe.paid_amount = so.grand_total
|
||||
pe.save(ignore_permissions=True)
|
||||
pe.submit()
|
||||
|
||||
# Make standalone advance payment entry
|
||||
create_payment_entry(
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party="QA Logistics",
|
||||
paid_from="Debtors - _TC",
|
||||
paid_to="_Test Bank - _TC",
|
||||
save=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
si = make_sales_invoice(so.name)
|
||||
|
||||
item = si.get("items")[1]
|
||||
si.remove(item)
|
||||
|
||||
si.allocate_advances_automatically = 1
|
||||
si.save()
|
||||
|
||||
self.assertEqual(len(si.get("advances")), 1)
|
||||
self.assertEqual(si.get("advances")[0].allocated_amount, 200)
|
||||
self.assertEqual(si.get("advances")[0].reference_name, pe.name)
|
||||
|
||||
si.submit()
|
||||
pe.load_from_db()
|
||||
|
||||
self.assertEqual(pe.references[0].reference_name, si.name)
|
||||
self.assertEqual(pe.references[0].allocated_amount, 200)
|
||||
self.assertEqual(pe.references[1].reference_name, so.name)
|
||||
self.assertEqual(pe.references[1].allocated_amount, 300)
|
||||
|
||||
|
||||
def automatically_fetch_payment_terms(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
|
@ -74,7 +74,35 @@ function get_filters() {
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"from_due_date",
|
||||
"label": __("From Due Date"),
|
||||
"fieldtype": "Date",
|
||||
},
|
||||
{
|
||||
"fieldname":"to_due_date",
|
||||
"label": __("To Due Date"),
|
||||
"fieldtype": "Date",
|
||||
},
|
||||
{
|
||||
"fieldname":"status",
|
||||
"label": __("Status"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
"width": 100,
|
||||
get_data: function(txt) {
|
||||
let status = ["Overdue", "Unpaid", "Completed", "Partly Paid"]
|
||||
let options = []
|
||||
for (let option of status){
|
||||
options.push({
|
||||
"value": option,
|
||||
"label": __(option),
|
||||
"description": ""
|
||||
})
|
||||
}
|
||||
return options
|
||||
}
|
||||
},
|
||||
]
|
||||
return filters;
|
||||
}
|
||||
|
@ -162,6 +162,12 @@ def build_filter_criterions(filters):
|
||||
if filters.item:
|
||||
qb_criterions.append(qb.DocType("Sales Order Item").item_code == filters.item)
|
||||
|
||||
if filters.from_due_date:
|
||||
qb_criterions.append(qb.DocType("Payment Schedule").due_date.gte(filters.from_due_date))
|
||||
|
||||
if filters.to_due_date:
|
||||
qb_criterions.append(qb.DocType("Payment Schedule").due_date.lte(filters.to_due_date))
|
||||
|
||||
return qb_criterions
|
||||
|
||||
|
||||
@ -279,11 +285,19 @@ def prepare_chart(s_orders):
|
||||
return chart
|
||||
|
||||
|
||||
def filter_on_calculated_status(filters, sales_orders):
|
||||
if filters.status and sales_orders:
|
||||
return [x for x in sales_orders if x.status in filters.status]
|
||||
return sales_orders
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns()
|
||||
sales_orders, so_invoices = get_so_with_invoices(filters)
|
||||
sales_orders, so_invoices = set_payment_terms_statuses(sales_orders, so_invoices, filters)
|
||||
|
||||
sales_orders = filter_on_calculated_status(filters, sales_orders)
|
||||
|
||||
prepare_chart(sales_orders)
|
||||
|
||||
data = sales_orders
|
||||
|
@ -2,7 +2,7 @@ import datetime
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days
|
||||
from frappe.utils import add_days, nowdate
|
||||
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
@ -77,12 +77,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
sinv.insert()
|
||||
sinv.submit()
|
||||
columns, data, message, chart = execute(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"period_start_date": "2021-06-01",
|
||||
"period_end_date": "2021-06-30",
|
||||
"item": item.item_code,
|
||||
}
|
||||
frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"period_start_date": "2021-06-01",
|
||||
"period_end_date": "2021-06-30",
|
||||
"item": item.item_code,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
expected_value = [
|
||||
@ -167,12 +169,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
sinv.insert()
|
||||
sinv.submit()
|
||||
columns, data, message, chart = execute(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"period_start_date": "2021-06-01",
|
||||
"period_end_date": "2021-06-30",
|
||||
"item": item.item_code,
|
||||
}
|
||||
frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"period_start_date": "2021-06-01",
|
||||
"period_end_date": "2021-06-30",
|
||||
"item": item.item_code,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# report defaults to company currency.
|
||||
@ -338,3 +342,60 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
with self.subTest(filters=filters):
|
||||
columns, data, message, chart = execute(filters)
|
||||
self.assertEqual(data, expected_values_for_group_filters[idx])
|
||||
|
||||
def test_04_due_date_filter(self):
|
||||
self.create_payment_terms_template()
|
||||
item = create_item(item_code="_Test Excavator 1", is_stock_item=0)
|
||||
transaction_date = nowdate()
|
||||
so = make_sales_order(
|
||||
transaction_date=add_days(transaction_date, -30),
|
||||
delivery_date=add_days(transaction_date, -15),
|
||||
item=item.item_code,
|
||||
qty=10,
|
||||
rate=100000,
|
||||
do_not_save=True,
|
||||
)
|
||||
so.po_no = ""
|
||||
so.taxes_and_charges = ""
|
||||
so.taxes = ""
|
||||
so.payment_terms_template = self.template.name
|
||||
so.save()
|
||||
so.submit()
|
||||
|
||||
# make invoice with 60% of the total sales order value
|
||||
sinv = make_sales_invoice(so.name)
|
||||
sinv.taxes_and_charges = ""
|
||||
sinv.taxes = ""
|
||||
sinv.items[0].qty = 6
|
||||
sinv.insert()
|
||||
sinv.submit()
|
||||
columns, data, message, chart = execute(
|
||||
frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"item": item.item_code,
|
||||
"from_due_date": add_days(transaction_date, -30),
|
||||
"to_due_date": add_days(transaction_date, -15),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
expected_value = [
|
||||
{
|
||||
"name": so.name,
|
||||
"customer": so.customer,
|
||||
"submitted": datetime.date.fromisoformat(add_days(transaction_date, -30)),
|
||||
"status": "Completed",
|
||||
"payment_term": None,
|
||||
"description": "_Test 50-50",
|
||||
"due_date": datetime.date.fromisoformat(add_days(transaction_date, -15)),
|
||||
"invoice_portion": 50.0,
|
||||
"currency": "INR",
|
||||
"base_payment_amount": 500000.0,
|
||||
"paid_amount": 500000.0,
|
||||
"invoices": "," + sinv.name,
|
||||
},
|
||||
]
|
||||
# Only the first term should be pulled
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertEqual(data, expected_value)
|
||||
|
@ -313,11 +313,13 @@ class Analytics(object):
|
||||
|
||||
def get_period(self, posting_date):
|
||||
if self.filters.range == "Weekly":
|
||||
period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year)
|
||||
period = _("Week {0} {1}").format(str(posting_date.isocalendar()[1]), str(posting_date.year))
|
||||
elif self.filters.range == "Monthly":
|
||||
period = str(self.months[posting_date.month - 1]) + " " + str(posting_date.year)
|
||||
period = _(str(self.months[posting_date.month - 1])) + " " + str(posting_date.year)
|
||||
elif self.filters.range == "Quarterly":
|
||||
period = "Quarter " + str(((posting_date.month - 1) // 3) + 1) + " " + str(posting_date.year)
|
||||
period = _("Quarter {0} {1}").format(
|
||||
str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)
|
||||
)
|
||||
else:
|
||||
year = get_fiscal_year(posting_date, company=self.filters.company)
|
||||
period = str(year[0])
|
||||
|
@ -291,7 +291,7 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
|
||||
batches = get_batches(item_code, warehouse, qty, throw, serial_no)
|
||||
|
||||
for batch in batches:
|
||||
if cint(qty) <= cint(batch.qty):
|
||||
if flt(qty) <= flt(batch.qty):
|
||||
batch_no = batch.batch_id
|
||||
break
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"barcode",
|
||||
"has_item_scanned",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"col_break1",
|
||||
@ -809,13 +810,21 @@
|
||||
"label": "Purchase Order Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "barcode",
|
||||
"fieldname": "has_item_scanned",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-26 16:05:17.720768",
|
||||
"modified": "2022-11-02 12:54:07.225623",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
@ -121,18 +121,24 @@ class InventoryDimension(Document):
|
||||
|
||||
if self.apply_to_all_doctypes:
|
||||
for doctype in get_inventory_documents():
|
||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||
else:
|
||||
if not field_exists(doctype[0], self.source_fieldname):
|
||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||
elif not field_exists(self.document_type, self.source_fieldname):
|
||||
custom_fields.setdefault(self.document_type, dimension_fields)
|
||||
|
||||
if not frappe.db.get_value(
|
||||
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
|
||||
):
|
||||
) and not field_exists("Stock Ledger Entry", self.target_fieldname):
|
||||
dimension_field = dimension_fields[1]
|
||||
dimension_field["fieldname"] = self.target_fieldname
|
||||
custom_fields["Stock Ledger Entry"] = dimension_field
|
||||
|
||||
create_custom_fields(custom_fields)
|
||||
if custom_fields:
|
||||
create_custom_fields(custom_fields)
|
||||
|
||||
|
||||
def field_exists(doctype, fieldname) -> str or None:
|
||||
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -191,6 +191,21 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
|
||||
self.assertEqual(sle_rack, "Rack 1")
|
||||
|
||||
def test_check_standard_dimensions(self):
|
||||
create_inventory_dimension(
|
||||
reference_document="Project",
|
||||
type_of_transaction="Outward",
|
||||
dimension_name="Project",
|
||||
apply_to_all_doctypes=0,
|
||||
document_type="Stock Ledger Entry",
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
frappe.db.get_value(
|
||||
"Custom Field", {"fieldname": "project", "dt": "Stock Ledger Entry"}, "name"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def prepare_test_data():
|
||||
if not frappe.db.exists("DocType", "Shelf"):
|
||||
|
@ -4,10 +4,13 @@ from frappe import _
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "material_request",
|
||||
"internal_links": {
|
||||
"Sales Order": ["items", "sales_order"],
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("Reference"),
|
||||
"items": ["Request for Quotation", "Supplier Quotation", "Purchase Order"],
|
||||
"items": ["Sales Order", "Request for Quotation", "Supplier Quotation", "Purchase Order"],
|
||||
},
|
||||
{"label": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]},
|
||||
{"label": _("Manufacturing"), "items": ["Work Order"]},
|
||||
|
@ -590,6 +590,7 @@ class TestMaterialRequest(FrappeTestCase):
|
||||
mr = frappe.copy_doc(test_records[0])
|
||||
mr.material_request_type = "Material Issue"
|
||||
mr.submit()
|
||||
frappe.db.value_cache = {}
|
||||
|
||||
# testing bin value after material request is submitted
|
||||
self.assertEqual(_get_requested_qty(), existing_requested_qty - 54.0)
|
||||
|
@ -10,6 +10,8 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.functions import Locate
|
||||
from frappe.utils import cint, floor, flt, today
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
|
||||
@ -686,31 +688,22 @@ def create_stock_entry(pick_list):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filters, as_dict):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
`name`, `company`, `planned_start_date`
|
||||
FROM
|
||||
`tabWork Order`
|
||||
WHERE
|
||||
`status` not in ('Completed', 'Stopped')
|
||||
AND `qty` > `material_transferred_for_manufacturing`
|
||||
AND `docstatus` = 1
|
||||
AND `company` = %(company)s
|
||||
AND `name` like %(txt)s
|
||||
ORDER BY
|
||||
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end) name
|
||||
LIMIT
|
||||
%(start)s, %(page_length)s""",
|
||||
{
|
||||
"txt": "%%%s%%" % txt,
|
||||
"_txt": txt.replace("%", ""),
|
||||
"start": start,
|
||||
"page_length": frappe.utils.cint(page_length),
|
||||
"company": filters.get("company"),
|
||||
},
|
||||
as_dict=as_dict,
|
||||
)
|
||||
wo = frappe.qb.DocType("Work Order")
|
||||
return (
|
||||
frappe.qb.from_(wo)
|
||||
.select(wo.name, wo.company, wo.planned_start_date)
|
||||
.where(
|
||||
(wo.status.notin(["Completed", "Stopped"]))
|
||||
& (wo.qty > wo.material_transferred_for_manufacturing)
|
||||
& (wo.docstatus == 1)
|
||||
& (wo.company == filters.get("company"))
|
||||
& (wo.name.like("%{0}%".format(txt)))
|
||||
)
|
||||
.orderby(Case().when(Locate(txt, wo.name) > 0, Locate(txt, wo.name)).else_(99999))
|
||||
.orderby(wo.name)
|
||||
.limit(cint(page_length))
|
||||
.offset(start)
|
||||
).run(as_dict=as_dict)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -8,6 +8,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"barcode",
|
||||
"has_item_scanned",
|
||||
"section_break_2",
|
||||
"item_code",
|
||||
"product_bundle",
|
||||
@ -996,12 +997,20 @@
|
||||
{
|
||||
"fieldname": "column_break_102",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "barcode",
|
||||
"fieldname": "has_item_scanned",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-26 16:06:02.524435",
|
||||
"modified": "2022-11-02 12:49:28.746701",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"barcode",
|
||||
"has_item_scanned",
|
||||
"section_break_2",
|
||||
"s_warehouse",
|
||||
"col_break1",
|
||||
@ -498,14 +499,14 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sco_rm_detail",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "SCO Supplied Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
"fieldname": "sco_rm_detail",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "SCO Supplied Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse",
|
||||
@ -563,13 +564,21 @@
|
||||
"fieldname": "is_process_loss",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Process Loss"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "barcode",
|
||||
"fieldname": "has_item_scanned",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-17 05:06:33.621264",
|
||||
"modified": "2022-11-02 13:00:34.258828",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
@ -7,6 +7,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"barcode",
|
||||
"has_item_scanned",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"warehouse",
|
||||
@ -177,11 +178,18 @@
|
||||
"label": "Allow Zero Valuation Rate",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "barcode",
|
||||
"fieldname": "has_item_scanned",
|
||||
"fieldtype": "Data",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-02 04:19:40.380587",
|
||||
"modified": "2022-11-02 13:01:23.580937",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reconciliation Item",
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import flt, getdate
|
||||
|
||||
|
||||
@ -11,8 +12,6 @@ def execute(filters=None):
|
||||
filters = {}
|
||||
float_precision = frappe.db.get_default("float_precision")
|
||||
|
||||
condition = get_condition(filters)
|
||||
|
||||
avg_daily_outgoing = 0
|
||||
diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1
|
||||
if diff <= 0:
|
||||
@ -20,8 +19,8 @@ def execute(filters=None):
|
||||
|
||||
columns = get_columns()
|
||||
items = get_item_info(filters)
|
||||
consumed_item_map = get_consumed_items(condition)
|
||||
delivered_item_map = get_delivered_items(condition)
|
||||
consumed_item_map = get_consumed_items(filters)
|
||||
delivered_item_map = get_delivered_items(filters)
|
||||
|
||||
data = []
|
||||
for item in items:
|
||||
@ -71,76 +70,86 @@ def get_columns():
|
||||
def get_item_info(filters):
|
||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||
|
||||
conditions = [get_item_group_condition(filters.get("item_group"))]
|
||||
if filters.get("brand"):
|
||||
conditions.append("item.brand=%(brand)s")
|
||||
conditions.append("is_stock_item = 1")
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select name, item_name, description, brand, item_group,
|
||||
safety_stock, lead_time_days from `tabItem` item where {}""".format(
|
||||
" and ".join(conditions)
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
item = frappe.qb.DocType("Item")
|
||||
query = (
|
||||
frappe.qb.from_(item)
|
||||
.select(
|
||||
item.name,
|
||||
item.item_name,
|
||||
item.description,
|
||||
item.brand,
|
||||
item.item_group,
|
||||
item.safety_stock,
|
||||
item.lead_time_days,
|
||||
)
|
||||
.where(item.is_stock_item == 1)
|
||||
)
|
||||
|
||||
if brand := filters.get("brand"):
|
||||
query = query.where(item.brand == brand)
|
||||
|
||||
def get_consumed_items(condition):
|
||||
if conditions := get_item_group_condition(filters.get("item_group"), item):
|
||||
query = query.where(conditions)
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
def get_consumed_items(filters):
|
||||
purpose_to_exclude = [
|
||||
"Material Transfer for Manufacture",
|
||||
"Material Transfer",
|
||||
"Send to Subcontractor",
|
||||
]
|
||||
|
||||
condition += """
|
||||
and (
|
||||
purpose is NULL
|
||||
or purpose not in ({})
|
||||
se = frappe.qb.DocType("Stock Entry")
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
query = (
|
||||
frappe.qb.from_(sle)
|
||||
.left_join(se)
|
||||
.on(sle.voucher_no == se.name)
|
||||
.select(sle.item_code, Abs(Sum(sle.actual_qty)).as_("consumed_qty"))
|
||||
.where(
|
||||
(sle.actual_qty < 0)
|
||||
& (sle.is_cancelled == 0)
|
||||
& (sle.voucher_type.notin(["Delivery Note", "Sales Invoice"]))
|
||||
& ((se.purpose.isnull()) | (se.purpose.notin(purpose_to_exclude)))
|
||||
)
|
||||
""".format(
|
||||
", ".join(f"'{p}'" for p in purpose_to_exclude)
|
||||
.groupby(sle.item_code)
|
||||
)
|
||||
condition = condition.replace("posting_date", "sle.posting_date")
|
||||
query = get_filtered_query(filters, sle, query)
|
||||
|
||||
consumed_items = frappe.db.sql(
|
||||
"""
|
||||
select item_code, abs(sum(actual_qty)) as consumed_qty
|
||||
from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
|
||||
on sle.voucher_no = se.name
|
||||
where
|
||||
actual_qty < 0
|
||||
and is_cancelled = 0
|
||||
and voucher_type not in ('Delivery Note', 'Sales Invoice')
|
||||
%s
|
||||
group by item_code"""
|
||||
% condition,
|
||||
as_dict=1,
|
||||
)
|
||||
consumed_items = query.run(as_dict=True)
|
||||
|
||||
consumed_items_map = {item.item_code: item.consumed_qty for item in consumed_items}
|
||||
return consumed_items_map
|
||||
|
||||
|
||||
def get_delivered_items(condition):
|
||||
dn_items = frappe.db.sql(
|
||||
"""select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty
|
||||
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
|
||||
where dn.name = dn_item.parent and dn.docstatus = 1 %s
|
||||
group by dn_item.item_code"""
|
||||
% (condition),
|
||||
as_dict=1,
|
||||
def get_delivered_items(filters):
|
||||
parent = frappe.qb.DocType("Delivery Note")
|
||||
child = frappe.qb.DocType("Delivery Note Item")
|
||||
query = (
|
||||
frappe.qb.from_(parent)
|
||||
.from_(child)
|
||||
.select(child.item_code, Sum(child.stock_qty).as_("dn_qty"))
|
||||
.where((parent.name == child.parent) & (parent.docstatus == 1))
|
||||
.groupby(child.item_code)
|
||||
)
|
||||
query = get_filtered_query(filters, parent, query)
|
||||
|
||||
si_items = frappe.db.sql(
|
||||
"""select si_item.item_code, sum(si_item.stock_qty) as si_qty
|
||||
from `tabSales Invoice` si, `tabSales Invoice Item` si_item
|
||||
where si.name = si_item.parent and si.docstatus = 1 and
|
||||
si.update_stock = 1 %s
|
||||
group by si_item.item_code"""
|
||||
% (condition),
|
||||
as_dict=1,
|
||||
dn_items = query.run(as_dict=True)
|
||||
|
||||
parent = frappe.qb.DocType("Sales Invoice")
|
||||
child = frappe.qb.DocType("Sales Invoice Item")
|
||||
query = (
|
||||
frappe.qb.from_(parent)
|
||||
.from_(child)
|
||||
.select(child.item_code, Sum(child.stock_qty).as_("si_qty"))
|
||||
.where((parent.name == child.parent) & (parent.docstatus == 1) & (parent.update_stock == 1))
|
||||
.groupby(child.item_code)
|
||||
)
|
||||
query = get_filtered_query(filters, parent, query)
|
||||
|
||||
si_items = query.run(as_dict=True)
|
||||
|
||||
dn_item_map = {}
|
||||
for item in dn_items:
|
||||
@ -152,13 +161,10 @@ def get_delivered_items(condition):
|
||||
return dn_item_map
|
||||
|
||||
|
||||
def get_condition(filters):
|
||||
conditions = ""
|
||||
def get_filtered_query(filters, table, query):
|
||||
if filters.get("from_date") and filters.get("to_date"):
|
||||
conditions += " and posting_date between '%s' and '%s'" % (
|
||||
filters["from_date"],
|
||||
filters["to_date"],
|
||||
)
|
||||
query = query.where(table.posting_date.between(filters["from_date"], filters["to_date"]))
|
||||
else:
|
||||
frappe.throw(_("From and To dates required"))
|
||||
return conditions
|
||||
frappe.throw(_("From and To dates are required"))
|
||||
|
||||
return query
|
||||
|
@ -4,7 +4,9 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import IfNull
|
||||
from frappe.utils import flt
|
||||
from pypika.terms import ExistsCriterion
|
||||
|
||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||
|
||||
@ -123,43 +125,65 @@ def get_items(filters):
|
||||
pb_details = frappe._dict()
|
||||
item_details = frappe._dict()
|
||||
|
||||
conditions = get_parent_item_conditions(filters)
|
||||
parent_item_details = frappe.db.sql(
|
||||
"""
|
||||
select item.name as item_code, item.item_name, pb.description, item.item_group, item.brand, item.stock_uom
|
||||
from `tabItem` item
|
||||
inner join `tabProduct Bundle` pb on pb.new_item_code = item.name
|
||||
where ifnull(item.disabled, 0) = 0 {0}
|
||||
""".format(
|
||||
conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
) # nosec
|
||||
item = frappe.qb.DocType("Item")
|
||||
pb = frappe.qb.DocType("Product Bundle")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(item)
|
||||
.inner_join(pb)
|
||||
.on(pb.new_item_code == item.name)
|
||||
.select(
|
||||
item.name.as_("item_code"),
|
||||
item.item_name,
|
||||
pb.description,
|
||||
item.item_group,
|
||||
item.brand,
|
||||
item.stock_uom,
|
||||
)
|
||||
.where(IfNull(item.disabled, 0) == 0)
|
||||
)
|
||||
|
||||
if item_code := filters.get("item_code"):
|
||||
query = query.where(item.item_code == item_code)
|
||||
else:
|
||||
if brand := filters.get("brand"):
|
||||
query = query.where(item.brand == brand)
|
||||
if item_group := filters.get("item_group"):
|
||||
if conditions := get_item_group_condition(item_group, item):
|
||||
query = query.where(conditions)
|
||||
|
||||
parent_item_details = query.run(as_dict=True)
|
||||
|
||||
parent_items = []
|
||||
for d in parent_item_details:
|
||||
parent_items.append(d.item_code)
|
||||
item_details[d.item_code] = d
|
||||
|
||||
child_item_details = []
|
||||
if parent_items:
|
||||
child_item_details = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
pb.new_item_code as parent_item, pbi.item_code, item.item_name, pbi.description, item.item_group, item.brand,
|
||||
item.stock_uom, pbi.uom, pbi.qty
|
||||
from `tabProduct Bundle Item` pbi
|
||||
inner join `tabProduct Bundle` pb on pb.name = pbi.parent
|
||||
inner join `tabItem` item on item.name = pbi.item_code
|
||||
where pb.new_item_code in ({0})
|
||||
""".format(
|
||||
", ".join(["%s"] * len(parent_items))
|
||||
),
|
||||
parent_items,
|
||||
as_dict=1,
|
||||
) # nosec
|
||||
else:
|
||||
child_item_details = []
|
||||
item = frappe.qb.DocType("Item")
|
||||
pb = frappe.qb.DocType("Product Bundle")
|
||||
pbi = frappe.qb.DocType("Product Bundle Item")
|
||||
|
||||
child_item_details = (
|
||||
frappe.qb.from_(pbi)
|
||||
.inner_join(pb)
|
||||
.on(pb.name == pbi.parent)
|
||||
.inner_join(item)
|
||||
.on(item.name == pbi.item_code)
|
||||
.select(
|
||||
pb.new_item_code.as_("parent_item"),
|
||||
pbi.item_code,
|
||||
item.item_name,
|
||||
pbi.description,
|
||||
item.item_group,
|
||||
item.brand,
|
||||
item.stock_uom,
|
||||
pbi.uom,
|
||||
pbi.qty,
|
||||
)
|
||||
.where(pb.new_item_code.isin(parent_items))
|
||||
).run(as_dict=1)
|
||||
|
||||
child_items = set()
|
||||
for d in child_item_details:
|
||||
@ -184,58 +208,42 @@ def get_stock_ledger_entries(filters, items):
|
||||
if not items:
|
||||
return []
|
||||
|
||||
item_conditions_sql = " and sle.item_code in ({})".format(
|
||||
", ".join(frappe.db.escape(i) for i in items)
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
sle2 = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(sle)
|
||||
.force_index("posting_sort_index")
|
||||
.left_join(sle2)
|
||||
.on(
|
||||
(sle.item_code == sle2.item_code)
|
||||
& (sle.warehouse == sle2.warehouse)
|
||||
& (sle.posting_date < sle2.posting_date)
|
||||
& (sle.posting_time < sle2.posting_time)
|
||||
& (sle.name < sle2.name)
|
||||
)
|
||||
.select(sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company)
|
||||
.where((sle2.name.isnull()) & (sle.docstatus < 2) & (sle.item_code.isin(items)))
|
||||
)
|
||||
|
||||
conditions = get_sle_conditions(filters)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company
|
||||
from
|
||||
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
||||
left join `tabStock Ledger Entry` sle2 on
|
||||
sle.item_code = sle2.item_code and sle.warehouse = sle2.warehouse
|
||||
and (sle.posting_date, sle.posting_time, sle.name) < (sle2.posting_date, sle2.posting_time, sle2.name)
|
||||
where sle2.name is null and sle.docstatus < 2 %s %s"""
|
||||
% (item_conditions_sql, conditions),
|
||||
as_dict=1,
|
||||
) # nosec
|
||||
|
||||
|
||||
def get_parent_item_conditions(filters):
|
||||
conditions = []
|
||||
|
||||
if filters.get("item_code"):
|
||||
conditions.append("item.item_code = %(item_code)s")
|
||||
if date := filters.get("date"):
|
||||
query = query.where(sle.posting_date <= date)
|
||||
else:
|
||||
if filters.get("brand"):
|
||||
conditions.append("item.brand=%(brand)s")
|
||||
if filters.get("item_group"):
|
||||
conditions.append(get_item_group_condition(filters.get("item_group")))
|
||||
|
||||
conditions = " and ".join(conditions)
|
||||
return "and {0}".format(conditions) if conditions else ""
|
||||
|
||||
|
||||
def get_sle_conditions(filters):
|
||||
conditions = ""
|
||||
if not filters.get("date"):
|
||||
frappe.throw(_("'Date' is required"))
|
||||
|
||||
conditions += " and sle.posting_date <= %s" % frappe.db.escape(filters.get("date"))
|
||||
|
||||
if filters.get("warehouse"):
|
||||
warehouse_details = frappe.db.get_value(
|
||||
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
||||
)
|
||||
if warehouse_details:
|
||||
conditions += (
|
||||
" and exists (select name from `tabWarehouse` wh \
|
||||
where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"
|
||||
% (warehouse_details.lft, warehouse_details.rgt)
|
||||
) # nosec
|
||||
|
||||
return conditions
|
||||
if warehouse_details:
|
||||
wh = frappe.qb.DocType("Warehouse")
|
||||
query = query.where(
|
||||
ExistsCriterion(
|
||||
frappe.qb.from_(wh)
|
||||
.select(wh.name)
|
||||
.where((wh.lft >= warehouse_details.lft) & (wh.rgt <= warehouse_details.rgt))
|
||||
)
|
||||
)
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
@ -198,11 +198,11 @@ def setup_ageing_columns(filters: Filters, range_columns: List):
|
||||
f"0 - {filters['range1']}",
|
||||
f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}",
|
||||
f"{cint(filters['range2']) + 1} - {cint(filters['range3'])}",
|
||||
f"{cint(filters['range3']) + 1} - {_('Above')}",
|
||||
_("{0} - Above").format(cint(filters["range3"]) + 1),
|
||||
]
|
||||
for i, label in enumerate(ranges):
|
||||
fieldname = "range" + str(i + 1)
|
||||
add_column(range_columns, label=f"Age ({label})", fieldname=fieldname)
|
||||
add_column(range_columns, label=_("Age ({0})").format(label), fieldname=fieldname)
|
||||
|
||||
|
||||
def add_column(
|
||||
|
@ -114,11 +114,13 @@ def get_period(posting_date, filters):
|
||||
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
|
||||
if filters.range == "Weekly":
|
||||
period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year)
|
||||
period = _("Week {0} {1}").format(str(posting_date.isocalendar()[1]), str(posting_date.year))
|
||||
elif filters.range == "Monthly":
|
||||
period = str(months[posting_date.month - 1]) + " " + str(posting_date.year)
|
||||
period = _(str(months[posting_date.month - 1])) + " " + str(posting_date.year)
|
||||
elif filters.range == "Quarterly":
|
||||
period = "Quarter " + str(((posting_date.month - 1) // 3) + 1) + " " + str(posting_date.year)
|
||||
period = _("Quarter {0} {1}").format(
|
||||
str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)
|
||||
)
|
||||
else:
|
||||
year = get_fiscal_year(posting_date, company=filters.company)
|
||||
period = str(year[2])
|
||||
|
@ -305,20 +305,25 @@ def get_inventory_dimension_fields():
|
||||
|
||||
|
||||
def get_items(filters):
|
||||
item = frappe.qb.DocType("Item")
|
||||
query = frappe.qb.from_(item).select(item.name)
|
||||
conditions = []
|
||||
if filters.get("item_code"):
|
||||
conditions.append("item.name=%(item_code)s")
|
||||
|
||||
if item_code := filters.get("item_code"):
|
||||
conditions.append(item.name == item_code)
|
||||
else:
|
||||
if filters.get("brand"):
|
||||
conditions.append("item.brand=%(brand)s")
|
||||
if filters.get("item_group"):
|
||||
conditions.append(get_item_group_condition(filters.get("item_group")))
|
||||
if brand := filters.get("brand"):
|
||||
conditions.append(item.brand == brand)
|
||||
if item_group := filters.get("item_group"):
|
||||
if condition := get_item_group_condition(item_group, item):
|
||||
conditions.append(condition)
|
||||
|
||||
items = []
|
||||
if conditions:
|
||||
items = frappe.db.sql_list(
|
||||
"""select name from `tabItem` item where {}""".format(" and ".join(conditions)), filters
|
||||
)
|
||||
for condition in conditions:
|
||||
query = query.where(condition)
|
||||
items = [r[0] for r in query.run()]
|
||||
|
||||
return items
|
||||
|
||||
|
||||
@ -330,29 +335,22 @@ def get_item_details(items, sl_entries, include_uom):
|
||||
if not items:
|
||||
return item_details
|
||||
|
||||
cf_field = cf_join = ""
|
||||
item = frappe.qb.DocType("Item")
|
||||
query = (
|
||||
frappe.qb.from_(item)
|
||||
.select(item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom)
|
||||
.where(item.name.isin(items))
|
||||
)
|
||||
|
||||
if include_uom:
|
||||
cf_field = ", ucd.conversion_factor"
|
||||
cf_join = (
|
||||
"left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s"
|
||||
% frappe.db.escape(include_uom)
|
||||
ucd = frappe.qb.DocType("UOM Conversion Detail")
|
||||
query = (
|
||||
query.left_join(ucd)
|
||||
.on((ucd.parent == item.name) & (ucd.uom == include_uom))
|
||||
.select(ucd.conversion_factor)
|
||||
)
|
||||
|
||||
res = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field}
|
||||
from
|
||||
`tabItem` item
|
||||
{cf_join}
|
||||
where
|
||||
item.name in ({item_codes})
|
||||
""".format(
|
||||
cf_field=cf_field, cf_join=cf_join, item_codes=",".join(["%s"] * len(items))
|
||||
),
|
||||
items,
|
||||
as_dict=1,
|
||||
)
|
||||
res = query.run(as_dict=True)
|
||||
|
||||
for item in res:
|
||||
item_details.setdefault(item.name, item)
|
||||
@ -427,16 +425,28 @@ def get_warehouse_condition(warehouse):
|
||||
return ""
|
||||
|
||||
|
||||
def get_item_group_condition(item_group):
|
||||
def get_item_group_condition(item_group, item_table=None):
|
||||
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
|
||||
if item_group_details:
|
||||
return (
|
||||
"item.item_group in (select ig.name from `tabItem Group` ig \
|
||||
where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
|
||||
% (item_group_details.lft, item_group_details.rgt)
|
||||
)
|
||||
|
||||
return ""
|
||||
if item_table:
|
||||
ig = frappe.qb.DocType("Item Group")
|
||||
return item_table.item_group.isin(
|
||||
(
|
||||
frappe.qb.from_(ig)
|
||||
.select(ig.name)
|
||||
.where(
|
||||
(ig.lft >= item_group_details.lft)
|
||||
& (ig.rgt <= item_group_details.rgt)
|
||||
& (item_table.item_group == ig.name)
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
return (
|
||||
"item.item_group in (select ig.name from `tabItem Group` ig \
|
||||
where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
|
||||
% (item_group_details.lft, item_group_details.rgt)
|
||||
)
|
||||
|
||||
|
||||
def check_inventory_dimension_filters_applied(filters) -> bool:
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user