Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-32846

This commit is contained in:
Raffael Meyer
2022-11-07 13:34:28 +01:00
committed by GitHub
17 changed files with 296 additions and 100 deletions

View File

@@ -57,6 +57,8 @@
"column_break_28", "column_break_28",
"total", "total",
"net_total", "net_total",
"tax_withholding_net_total",
"base_tax_withholding_net_total",
"taxes_section", "taxes_section",
"taxes_and_charges", "taxes_and_charges",
"column_break_58", "column_break_58",
@@ -89,7 +91,6 @@
"section_break_44", "section_break_44",
"apply_discount_on", "apply_discount_on",
"base_discount_amount", "base_discount_amount",
"additional_discount_account",
"column_break_46", "column_break_46",
"additional_discount_percentage", "additional_discount_percentage",
"discount_amount", "discount_amount",
@@ -1421,6 +1422,26 @@
"label": "Is Old Subcontracting Flow", "label": "Is Old Subcontracting Flow",
"read_only": 1 "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", "collapsible_depends_on": "tax_withheld_vouchers",
"fieldname": "tax_withheld_vouchers_section", "fieldname": "tax_withheld_vouchers_section",
@@ -1519,7 +1540,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-10-11 13:04:44.304389", "modified": "2022-11-04 01:02:44.544878",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -40,6 +40,7 @@
"discount_amount", "discount_amount",
"base_rate_with_margin", "base_rate_with_margin",
"sec_break2", "sec_break2",
"apply_tds",
"rate", "rate",
"amount", "amount",
"item_tax_template", "item_tax_template",
@@ -868,6 +869,12 @@
"label": "Product Bundle", "label": "Product Bundle",
"options": "Product Bundle", "options": "Product Bundle",
"read_only": 1 "read_only": 1
},
{
"default": "1",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply TDS"
} }
], ],
"idx": 1, "idx": 1,

View File

@@ -61,6 +61,9 @@ def get_party_details(inv):
def get_party_tax_withholding_details(inv, tax_withholding_category=None): 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 = "" pan_no = ""
parties = [] parties = []
party_type, party = get_party_details(inv) 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": if party_type == "Supplier":
ldc = get_lower_deduction_certificate(tax_details, pan_no) ldc = get_lower_deduction_certificate(tax_details, pan_no)
if tax_deducted: if tax_deducted:
net_total = inv.net_total net_total = inv.tax_withholding_net_total
if ldc: if ldc:
tax_amount = get_tds_amount_from_ldc( tax_amount = get_tds_amount_from_ldc(
ldc, parties, pan_no, tax_details, posting_date, net_total 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"): def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" 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 = {} voucher_wise_amount = {}
vouchers = [] 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")} {"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: for d in invoices_details:
vouchers.append(d.name) vouchers.append(d.name)
@@ -392,7 +400,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
tds_amount = 0 tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} 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): if cint(tax_details.consider_party_ledger_amount):
invoice_filters.pop("apply_tds", None) 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 += supp_jv_credit_amt
supp_credit_amt += inv.net_total supp_credit_amt += inv.tax_withholding_net_total
threshold = tax_details.get("threshold", 0) threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_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 cumulative_threshold and supp_credit_amt >= cumulative_threshold
): ):
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint( 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 # Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach # Grand is used to just check for threshold breach
net_total = 0 net_total = (
if vouchers: frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)")
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0
)
net_total += inv.net_total net_total += inv.tax_withholding_net_total
supp_credit_amt = net_total - cumulative_threshold supp_credit_amt = net_total - cumulative_threshold
if ldc and is_valid_certificate( 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, ldc.valid_upto,
inv.get("posting_date") or inv.get("transaction_date"), inv.get("posting_date") or inv.get("transaction_date"),
tax_deducted, tax_deducted,
inv.net_total, inv.tax_withholding_net_total,
ldc.certificate_limit, ldc.certificate_limit,
): ):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) 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( limit_consumed = frappe.db.get_value(
"Purchase Invoice", "Purchase Invoice",
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1}, {"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
"sum(net_total)", "sum(tax_withholding_net_total)",
) )
if is_valid_certificate( if is_valid_certificate(

View File

@@ -186,6 +186,46 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices): for d in reversed(invoices):
d.cancel() 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): def test_multi_category_single_supplier(self):
frappe.db.set_value( frappe.db.set_value(
"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category" "Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"

View File

@@ -227,7 +227,7 @@ class AccountsController(TransactionBase):
for item in self.get("items"): for item in self.get("items"):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
if not item.get(field_map.get(self.doctype)): if not item.get(field_map.get(self.doctype)):
default_deferred_account = frappe.db.get_value( default_deferred_account = frappe.get_cached_value(
"Company", self.company, "default_" + field_map.get(self.doctype) "Company", self.company, "default_" + field_map.get(self.doctype)
) )
if not default_deferred_account: if not default_deferred_account:
@@ -676,7 +676,7 @@ class AccountsController(TransactionBase):
def validate_enabled_taxes_and_charges(self): def validate_enabled_taxes_and_charges(self):
taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges") taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges")
if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"): if frappe.get_cached_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"):
frappe.throw( frappe.throw(
_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges) _("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges)
) )
@@ -684,7 +684,7 @@ class AccountsController(TransactionBase):
def validate_tax_account_company(self): def validate_tax_account_company(self):
for d in self.get("taxes"): for d in self.get("taxes"):
if d.account_head: if d.account_head:
tax_account_company = frappe.db.get_value("Account", d.account_head, "company") tax_account_company = frappe.get_cached_value("Account", d.account_head, "company")
if tax_account_company != self.company: if tax_account_company != self.company:
frappe.throw( frappe.throw(
_("Row #{0}: Account {1} does not belong to company {2}").format( _("Row #{0}: Account {1} does not belong to company {2}").format(
@@ -929,7 +929,9 @@ class AccountsController(TransactionBase):
party_account = self.credit_to if is_purchase_invoice else self.debit_to party_account = self.credit_to if is_purchase_invoice else self.debit_to
party_type = "Supplier" if is_purchase_invoice else "Customer" party_type = "Supplier" if is_purchase_invoice else "Customer"
gain_loss_account = frappe.db.get_value("Company", self.company, "exchange_gain_loss_account") gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
if not gain_loss_account: if not gain_loss_account:
frappe.throw( frappe.throw(
_("Please set default Exchange Gain/Loss Account in Company {}").format(self.get("company")) _("Please set default Exchange Gain/Loss Account in Company {}").format(self.get("company"))
@@ -1026,7 +1028,7 @@ class AccountsController(TransactionBase):
else self.grand_total else self.grand_total
), ),
"outstanding_amount": self.outstanding_amount, "outstanding_amount": self.outstanding_amount,
"difference_account": frappe.db.get_value( "difference_account": frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account" "Company", self.company, "exchange_gain_loss_account"
), ),
"exchange_gain_loss": flt(d.get("exchange_gain_loss")), "exchange_gain_loss": flt(d.get("exchange_gain_loss")),
@@ -1394,7 +1396,7 @@ class AccountsController(TransactionBase):
@property @property
def company_abbr(self): def company_abbr(self):
if not hasattr(self, "_abbr"): if not hasattr(self, "_abbr"):
self._abbr = frappe.db.get_value("Company", self.company, "abbr") self._abbr = frappe.get_cached_value("Company", self.company, "abbr")
return self._abbr return self._abbr
@@ -1780,7 +1782,7 @@ class AccountsController(TransactionBase):
""" """
if self.is_internal_transfer() and not self.unrealized_profit_loss_account: if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
unrealized_profit_loss_account = frappe.db.get_value( unrealized_profit_loss_account = frappe.get_cached_value(
"Company", self.company, "unrealized_profit_loss_account" "Company", self.company, "unrealized_profit_loss_account"
) )
@@ -1895,7 +1897,9 @@ class AccountsController(TransactionBase):
@frappe.whitelist() @frappe.whitelist()
def get_tax_rate(account_head): def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) return frappe.get_cached_value(
"Account", account_head, ["tax_rate", "account_name"], as_dict=True
)
@frappe.whitelist() @frappe.whitelist()
@@ -1904,7 +1908,7 @@ def get_default_taxes_and_charges(master_doctype, tax_template=None, company=Non
return {} return {}
if tax_template and company: if tax_template and company:
tax_template_company = frappe.db.get_value(master_doctype, tax_template, "company") tax_template_company = frappe.get_cached_value(master_doctype, tax_template, "company")
if tax_template_company == company: if tax_template_company == company:
return return

View File

@@ -326,7 +326,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
company = frappe.db.get_value("Delivery Note", source_name, "company") company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value( default_warehouse_for_sales_return = frappe.get_cached_value(
"Company", company, "default_warehouse_for_sales_return" "Company", company, "default_warehouse_for_sales_return"
) )
@@ -340,11 +340,11 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
# look for Print Heading "Credit Note" # look for Print Heading "Credit Note"
if not doc.select_print_heading: if not doc.select_print_heading:
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Credit Note")) doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Credit Note"))
elif doctype == "Purchase Invoice": elif doctype == "Purchase Invoice":
# look for Print Heading "Debit Note" # look for Print Heading "Debit Note"
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Debit Note")) doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Debit Note"))
for tax in doc.get("taxes") or []: for tax in doc.get("taxes") or []:
if tax.charge_type == "Actual": if tax.charge_type == "Actual":

View File

@@ -57,7 +57,7 @@ class StockController(AccountsController):
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
provisional_accounting_for_non_stock_items = cint( provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value( frappe.get_cached_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items" "Company", self.company, "enable_provisional_accounting_for_non_stock_items"
) )
) )
@@ -200,7 +200,7 @@ class StockController(AccountsController):
elif self.get("is_internal_supplier"): elif self.get("is_internal_supplier"):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"] warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.db.get_value("Company", self.company, "default_expense_account") expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
gl_list.append( gl_list.append(
self.get_gl_dict( self.get_gl_dict(
@@ -235,7 +235,7 @@ class StockController(AccountsController):
if warehouse_with_no_account: if warehouse_with_no_account:
for wh in warehouse_with_no_account: for wh in warehouse_with_no_account:
if frappe.db.get_value("Warehouse", wh, "company"): if frappe.get_cached_value("Warehouse", wh, "company"):
frappe.throw( frappe.throw(
_( _(
"Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}." "Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}."
@@ -449,15 +449,15 @@ class StockController(AccountsController):
# Get value based on doctype name # Get value based on doctype name
if not sl_dict.get(dimension.target_fieldname): if not sl_dict.get(dimension.target_fieldname):
fieldname = frappe.get_cached_value( fieldname = next(
"DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname" (
field.fieldname
for field in frappe.get_meta(self.doctype).fields
if field.options == dimension.fetch_from_parent
),
None,
) )
if not fieldname:
fieldname = frappe.get_cached_value(
"Custom Field", {"dt": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
)
if fieldname and self.get(fieldname): if fieldname and self.get(fieldname):
sl_dict[dimension.target_fieldname] = self.get(fieldname) sl_dict[dimension.target_fieldname] = self.get(fieldname)

View File

@@ -100,7 +100,7 @@ class SubcontractingController(StockController):
and self._doc_before_save and self._doc_before_save
): ):
for row in self._doc_before_save.get("items"): for row in self._doc_before_save.get("items"):
item_dict[row.name] = (row.item_code, row.qty) item_dict[row.name] = (row.item_code, row.received_qty or row.qty)
return item_dict return item_dict
@@ -118,7 +118,9 @@ class SubcontractingController(StockController):
for row in self.items: for row in self.items:
self.__reference_name.append(row.name) self.__reference_name.append(row.name)
if (row.name not in item_dict) or (row.item_code, row.qty) != item_dict[row.name]: if (row.name not in item_dict) or (row.item_code, row.received_qty or row.qty) != item_dict[
row.name
]:
self.__changed_name.append(row.name) self.__changed_name.append(row.name)
if item_dict.get(row.name): if item_dict.get(row.name):
@@ -461,12 +463,13 @@ class SubcontractingController(StockController):
def __get_qty_based_on_material_transfer(self, item_row, transfer_item): def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field)) key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
item_qty = item_row.received_qty or item_row.qty
if self.qty_to_be_received == item_row.qty: if self.qty_to_be_received.get(key) == item_qty:
return transfer_item.qty return transfer_item.qty
if self.qty_to_be_received: if self.qty_to_be_received:
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0)) qty = (flt(item_qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
transfer_item.item_details.required_qty = transfer_item.qty transfer_item.item_details.required_qty = transfer_item.qty
if transfer_item.serial_no or frappe.get_cached_value( if transfer_item.serial_no or frappe.get_cached_value(
@@ -491,7 +494,11 @@ class SubcontractingController(StockController):
for bom_item in self.__get_materials_from_bom( for bom_item in self.__get_materials_from_bom(
row.item_code, row.bom, row.get("include_exploded_items") row.item_code, row.bom, row.get("include_exploded_items")
): ):
qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor qty = (
flt(bom_item.qty_consumed_per_unit)
* flt(row.received_qty or row.qty)
* row.conversion_factor
)
bom_item.main_item_code = row.item_code bom_item.main_item_code = row.item_code
self.__update_reserve_warehouse(bom_item, row) self.__update_reserve_warehouse(bom_item, row)
self.__set_alternative_item(bom_item) self.__set_alternative_item(bom_item)

View File

@@ -58,12 +58,25 @@ class calculate_taxes_and_totals(object):
self.initialize_taxes() self.initialize_taxes()
self.determine_exclusive_rate() self.determine_exclusive_rate()
self.calculate_net_total() self.calculate_net_total()
self.calculate_tax_withholding_net_total()
self.calculate_taxes() self.calculate_taxes()
self.manipulate_grand_total_for_inclusive_tax() self.manipulate_grand_total_for_inclusive_tax()
self.calculate_totals() self.calculate_totals()
self._cleanup() self._cleanup()
self.calculate_total_net_weight() 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): def validate_item_tax_template(self):
for item in self.doc.get("items"): for item in self.doc.get("items"):
if item.item_code and item.get("item_tax_template"): if item.item_code and item.get("item_tax_template"):
@@ -1043,7 +1056,7 @@ class init_landed_taxes_and_totals(object):
company_currency = erpnext.get_company_currency(self.doc.company) company_currency = erpnext.get_company_currency(self.doc.company)
for d in self.doc.get(self.tax_field): for d in self.doc.get(self.tax_field):
if not d.account_currency: if not d.account_currency:
account_currency = frappe.db.get_value("Account", d.expense_account, "account_currency") account_currency = frappe.get_cached_value("Account", d.expense_account, "account_currency")
d.account_currency = account_currency or company_currency d.account_currency = account_currency or company_currency
def set_exchange_rate(self): def set_exchange_rate(self):

View File

@@ -80,7 +80,7 @@ def get_data(filters, conditions):
if conditions.get("trans") == "Quotation" and filters.get("group_by") == "Customer": if conditions.get("trans") == "Quotation" and filters.get("group_by") == "Customer":
cond += " and t1.quotation_to = 'Customer'" cond += " and t1.quotation_to = 'Customer'"
year_start_date, year_end_date = frappe.db.get_value( year_start_date, year_end_date = frappe.get_cached_value(
"Fiscal Year", filters.get("fiscal_year"), ["year_start_date", "year_end_date"] "Fiscal Year", filters.get("fiscal_year"), ["year_start_date", "year_end_date"]
) )
@@ -275,7 +275,7 @@ def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
if not year_start_date: if not year_start_date:
year_start_date, year_end_date = frappe.db.get_value( year_start_date, year_end_date = frappe.get_cached_value(
"Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"] "Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
) )

View File

@@ -589,66 +589,69 @@ erpnext.work_order = {
} }
} }
if(!frm.doc.skip_transfer){ if (frm.doc.status != 'Stopped') {
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption // If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') { if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { if (flt(doc.material_transferred_for_manufacturing) > 0 || frm.doc.skip_transfer) {
frm.has_finish_btn = true; // Only show "Material Consumption" when required_qty > consumed_qty
var counter = 0;
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { var tbl = frm.doc.required_items || [];
// Only show "Material Consumption" when required_qty > consumed_qty var tbl_lenght = tbl.length;
var counter = 0; for (var i = 0, len = tbl_lenght; i < len; i++) {
var tbl = frm.doc.required_items || []; let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
var tbl_lenght = tbl.length; if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
for (var i = 0, len = tbl_lenght; i < len; i++) { counter += 1;
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1;
}
}
if (counter > 0) {
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
});
consumption_btn.addClass('btn-primary');
} }
} }
if (counter > 0) {
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
});
consumption_btn.addClass('btn-primary');
}
}
}
if(!frm.doc.skip_transfer){
if (flt(doc.material_transferred_for_manufacturing) > 0) {
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
frm.has_finish_btn = true;
var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
if(doc.material_transferred_for_manufacturing>=doc.qty) {
// all materials transferred for manufacturing, make this primary
finish_btn.addClass('btn-primary');
}
} else {
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
let allowance_percentage = doc.overproduction_percentage_for_work_order;
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
}
});
}
}
} else {
if ((flt(doc.produced_qty) < flt(doc.qty))) {
var finish_btn = frm.add_custom_button(__('Finish'), function() { var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture'); erpnext.work_order.make_se(frm, 'Manufacture');
}); });
finish_btn.addClass('btn-primary');
if(doc.material_transferred_for_manufacturing>=doc.qty) {
// all materials transferred for manufacturing, make this primary
finish_btn.addClass('btn-primary');
}
} else {
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
let allowance_percentage = doc.overproduction_percentage_for_work_order;
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
}
});
} }
} }
} else {
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
finish_btn.addClass('btn-primary');
}
} }
} }
}, },
calculate_cost: function(doc) { calculate_cost: function(doc) {
if (doc.operations){ if (doc.operations){

View File

@@ -317,4 +317,4 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
erpnext.patches.v13_0.update_schedule_type_in_loans erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v14_0.update_tds_fields

View 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

View File

@@ -1207,7 +1207,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"base_rounding_adjustment"], company_currency); "base_rounding_adjustment"], company_currency);
this.frm.set_currency_labels(["total", "net_total", "total_taxes_and_charges", "discount_amount", 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", "rounded_total", "in_words", "paid_amount", "write_off_amount", "operating_cost",
"scrap_material_cost", "rounding_adjustment", "raw_material_cost", "scrap_material_cost", "rounding_adjustment", "raw_material_cost",
"total_cost"], this.frm.doc.currency); "total_cost"], this.frm.doc.currency);
@@ -1224,7 +1224,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} }
// toggle fields // 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_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_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", "base_paid_amount", "base_write_off_amount", "base_operating_cost", "base_raw_material_cost",

View File

@@ -91,8 +91,8 @@ def get_columns(filters):
columns = [ columns = [
_("Item") + ":Link/Item:180", _("Item") + ":Link/Item:180",
_("Item Group") + "::100", _("Item Group") + "::100",
_("Value") + ":Currency:100", _("Value") + ":Currency:120",
_("Age") + ":Float:60", _("Age") + ":Float:80",
] ]
return columns return columns
@@ -123,7 +123,7 @@ def get_warehouse_list(filters):
def add_warehouse_column(columns, warehouse_list): def add_warehouse_column(columns, warehouse_list):
if len(warehouse_list) > 1: if len(warehouse_list) > 1:
columns += [_("Total Qty") + ":Int:50"] columns += [_("Total Qty") + ":Int:90"]
for wh in warehouse_list: for wh in warehouse_list:
columns += [_(wh.name) + ":Int:54"] columns += [_(wh.name) + ":Int:120"]

View File

@@ -58,6 +58,7 @@ class SubcontractingReceipt(SubcontractingController):
def before_validate(self): def before_validate(self):
super(SubcontractingReceipt, self).before_validate() super(SubcontractingReceipt, self).before_validate()
self.set_items_bom() self.set_items_bom()
self.set_received_qty()
self.set_items_cost_center() self.set_items_cost_center()
self.set_items_expense_account() self.set_items_expense_account()
@@ -212,6 +213,10 @@ class SubcontractingReceipt(SubcontractingController):
"bom", "bom",
) )
def set_received_qty(self):
for item in self.items:
item.received_qty = flt(item.qty) + flt(item.rejected_qty)
def set_items_cost_center(self): def set_items_cost_center(self):
if self.company: if self.company:
cost_center = frappe.get_cached_value("Company", self.company, "cost_center") cost_center = frappe.get_cached_value("Company", self.company, "cost_center")

View File

@@ -468,6 +468,65 @@ class TestSubcontractingReceipt(FrappeTestCase):
scr.cancel() scr.cancel()
self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name)) self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name))
def test_supplied_items_consumed_qty(self):
# Set Backflush Based On as "Material Transferred for Subcontracting" to transfer RM's more than the required qty
set_backflush_based_on("Material Transferred for Subcontract")
# Create Material Receipt for RM's
make_stock_entry(
item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100
)
make_stock_entry(
item_code="_Test Item Home Desktop 100",
qty=100,
target="_Test Warehouse 1 - _TC",
basic_rate=100,
)
service_items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 1",
"qty": 10,
"rate": 100,
"fg_item": "_Test FG Item",
"fg_item_qty": 10,
},
]
# Create Subcontracting Order
sco = get_subcontracting_order(service_items=service_items)
# Transfer RM's
rm_items = get_rm_items(sco.supplied_items)
rm_items[0]["qty"] = 20 # Extra 10 Qty
itemwise_details = make_stock_in_entry(rm_items=rm_items)
make_stock_transfer_entry(
sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
# Create Subcontracting Receipt
scr = make_subcontracting_receipt(sco.name)
scr.rejected_warehouse = "_Test Warehouse 1 - _TC"
scr.items[0].qty = 5 # Accepted Qty
scr.items[0].rejected_qty = 3
scr.save()
# consumed_qty should be ((received_qty) * (transfered_qty / qty)) = ((5 + 3) * (20 / 10)) = 16
self.assertEqual(scr.supplied_items[0].consumed_qty, 16)
# Set Backflush Based On as "BOM"
set_backflush_based_on("BOM")
scr.items[0].rejected_qty = 4
scr.save()
# consumed_qty should be ((received_qty) * (qty_consumed_per_unit)) = ((5 + 4) * (1)) = 9
self.assertEqual(scr.supplied_items[0].consumed_qty, 9)
def make_return_subcontracting_receipt(**args): def make_return_subcontracting_receipt(**args):
args = frappe._dict(args) args = frappe._dict(args)