mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-22 14:39:19 +00:00
Merge branch 'develop' into unreconcile-bank-transaction-on-cancel
This commit is contained in:
@@ -129,6 +129,17 @@ class AccountsController(TransactionBase):
|
||||
if self.doctype in relevant_docs:
|
||||
self.set_payment_schedule()
|
||||
|
||||
def remove_bundle_for_non_stock_invoices(self):
|
||||
has_sabb = False
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.update_stock:
|
||||
for item in self.get("items"):
|
||||
if item.serial_and_batch_bundle:
|
||||
item.serial_and_batch_bundle = None
|
||||
has_sabb = True
|
||||
|
||||
if has_sabb:
|
||||
self.remove_serial_and_batch_bundle()
|
||||
|
||||
def ensure_supplier_is_not_blocked(self):
|
||||
is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
|
||||
is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
|
||||
@@ -156,6 +167,9 @@ class AccountsController(TransactionBase):
|
||||
if self.get("_action") and self._action != "update_after_submit":
|
||||
self.set_missing_values(for_validate=True)
|
||||
|
||||
if self.get("_action") == "submit":
|
||||
self.remove_bundle_for_non_stock_invoices()
|
||||
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
|
||||
self.validate_date_with_fiscal_year()
|
||||
@@ -225,6 +239,11 @@ class AccountsController(TransactionBase):
|
||||
apply_pricing_rule_on_transaction(self)
|
||||
|
||||
self.set_total_in_words()
|
||||
self.set_default_letter_head()
|
||||
|
||||
def set_default_letter_head(self):
|
||||
if hasattr(self, "letter_head") and not self.letter_head:
|
||||
self.letter_head = frappe.db.get_value("Company", self.company, "default_letter_head")
|
||||
|
||||
def init_internal_values(self):
|
||||
# init all the internal values as 0 on sa
|
||||
@@ -556,18 +575,12 @@ class AccountsController(TransactionBase):
|
||||
validate_due_date(
|
||||
self.posting_date,
|
||||
self.due_date,
|
||||
"Customer",
|
||||
self.customer,
|
||||
self.company,
|
||||
self.payment_terms_template,
|
||||
)
|
||||
elif self.doctype == "Purchase Invoice":
|
||||
validate_due_date(
|
||||
self.bill_date or self.posting_date,
|
||||
self.due_date,
|
||||
"Supplier",
|
||||
self.supplier,
|
||||
self.company,
|
||||
self.bill_date,
|
||||
self.payment_terms_template,
|
||||
)
|
||||
@@ -874,6 +887,7 @@ class AccountsController(TransactionBase):
|
||||
"project": self.get("project"),
|
||||
"post_net_value": args.get("post_net_value"),
|
||||
"voucher_detail_no": args.get("voucher_detail_no"),
|
||||
"voucher_subtype": self.get_voucher_subtype(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -916,7 +930,7 @@ class AccountsController(TransactionBase):
|
||||
# Update details in transaction currency
|
||||
gl_dict.update(
|
||||
{
|
||||
"transaction_currency": self.get("currency") or self.company_currency,
|
||||
"transaction_currency": args.get("currency") or self.get("currency") or self.company_currency,
|
||||
"transaction_exchange_rate": self.get("conversion_rate", 1),
|
||||
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||
account_currency, args, "debit"
|
||||
@@ -927,22 +941,49 @@ class AccountsController(TransactionBase):
|
||||
}
|
||||
)
|
||||
|
||||
if not args.get("against_voucher_type") and self.get("against_voucher_type"):
|
||||
gl_dict.update({"against_voucher_type": self.get("against_voucher_type")})
|
||||
|
||||
if not args.get("against_voucher") and self.get("against_voucher"):
|
||||
gl_dict.update({"against_voucher": self.get("against_voucher")})
|
||||
|
||||
return gl_dict
|
||||
|
||||
def get_voucher_subtype(self):
|
||||
voucher_subtypes = {
|
||||
"Journal Entry": "voucher_type",
|
||||
"Payment Entry": "payment_type",
|
||||
"Stock Entry": "stock_entry_type",
|
||||
"Asset Capitalization": "entry_type",
|
||||
}
|
||||
if self.doctype in voucher_subtypes:
|
||||
return self.get(voucher_subtypes[self.doctype])
|
||||
elif self.doctype == "Purchase Receipt" and self.is_return:
|
||||
return "Purchase Return"
|
||||
elif self.doctype == "Delivery Note" and self.is_return:
|
||||
return "Sales Return"
|
||||
elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice":
|
||||
return "Credit Note"
|
||||
elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice":
|
||||
return "Debit Note"
|
||||
return self.doctype
|
||||
|
||||
def get_value_in_transaction_currency(self, account_currency, args, field):
|
||||
if account_currency == self.get("currency"):
|
||||
if account_currency == args.get("currency") or self.get("currency"):
|
||||
return args.get(field + "_in_account_currency")
|
||||
else:
|
||||
return flt(args.get(field, 0) / self.get("conversion_rate", 1))
|
||||
return flt(args.get(field, 0) / (args.get("conversion_rate") or self.get("conversion_rate", 1)))
|
||||
|
||||
def validate_qty_is_not_zero(self):
|
||||
if self.doctype == "Purchase Receipt":
|
||||
return
|
||||
|
||||
for item in self.items:
|
||||
if self.doctype == "Purchase Receipt" and item.rejected_qty:
|
||||
continue
|
||||
|
||||
if not flt(item.qty):
|
||||
frappe.throw(
|
||||
msg=_("Row #{0}: Item quantity cannot be zero").format(item.idx),
|
||||
msg=_("Row #{0}: Quantity for Item {1} cannot be zero.").format(
|
||||
item.idx, frappe.bold(item.item_code)
|
||||
),
|
||||
title=_("Invalid Quantity"),
|
||||
exc=InvalidQtyError,
|
||||
)
|
||||
@@ -1120,6 +1161,7 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit"
|
||||
against_type = "Supplier" if self.doctype == "Purchase Invoice" else "Customer"
|
||||
against = self.supplier if self.doctype == "Purchase Invoice" else self.customer
|
||||
|
||||
if precision_loss:
|
||||
@@ -1127,7 +1169,9 @@ class AccountsController(TransactionBase):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": round_off_account,
|
||||
"against_type": against_type,
|
||||
"against": against,
|
||||
"against_link": against,
|
||||
credit_or_debit: precision_loss,
|
||||
"cost_center": round_off_cost_center
|
||||
if self.use_company_roundoff_cost_center
|
||||
@@ -1481,11 +1525,13 @@ class AccountsController(TransactionBase):
|
||||
if self.doctype == "Purchase Invoice":
|
||||
dr_or_cr = "credit"
|
||||
rev_dr_cr = "debit"
|
||||
against_type = "Supplier"
|
||||
supplier_or_customer = self.supplier
|
||||
|
||||
else:
|
||||
dr_or_cr = "debit"
|
||||
rev_dr_cr = "credit"
|
||||
against_type = "Customer"
|
||||
supplier_or_customer = self.customer
|
||||
|
||||
if enable_discount_accounting:
|
||||
@@ -1510,7 +1556,9 @@ class AccountsController(TransactionBase):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.discount_account,
|
||||
"against_type": against_type,
|
||||
"against": supplier_or_customer,
|
||||
"against_link": supplier_or_customer,
|
||||
dr_or_cr: flt(
|
||||
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
|
||||
),
|
||||
@@ -1528,7 +1576,9 @@ class AccountsController(TransactionBase):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": income_or_expense_account,
|
||||
"against_type": against_type,
|
||||
"against": supplier_or_customer,
|
||||
"against_link": supplier_or_customer,
|
||||
rev_dr_cr: flt(
|
||||
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
|
||||
),
|
||||
@@ -1551,7 +1601,9 @@ class AccountsController(TransactionBase):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against_type": against_type,
|
||||
"against": supplier_or_customer,
|
||||
"against_link": supplier_or_customer,
|
||||
dr_or_cr: self.base_discount_amount,
|
||||
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
|
||||
},
|
||||
@@ -2400,6 +2452,7 @@ def validate_taxes_and_charges(tax):
|
||||
|
||||
def validate_account_head(idx, account, company, context=""):
|
||||
account_company = frappe.get_cached_value("Account", account, "company")
|
||||
is_group = frappe.get_cached_value("Account", account, "is_group")
|
||||
|
||||
if account_company != company:
|
||||
frappe.throw(
|
||||
@@ -2409,6 +2462,12 @@ def validate_account_head(idx, account, company, context=""):
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
if is_group:
|
||||
frappe.throw(
|
||||
_("Row {0}: Account {1} is a Group Account").format(idx, frappe.bold(account)),
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
|
||||
def validate_cost_center(tax, doc):
|
||||
if not tax.cost_center:
|
||||
@@ -3045,7 +3104,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
def validate_quantity(child_item, new_data):
|
||||
if not flt(new_data.get("qty")):
|
||||
frappe.throw(
|
||||
_("Row # {0}: Quantity for Item {1} cannot be zero").format(
|
||||
_("Row #{0}: Quantity for Item {1} cannot be zero.").format(
|
||||
new_data.get("idx"), frappe.bold(new_data.get("item_code"))
|
||||
),
|
||||
title=_("Invalid Qty"),
|
||||
|
||||
@@ -381,7 +381,11 @@ class BuyingController(SubcontractingController):
|
||||
|
||||
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||
else:
|
||||
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
|
||||
field = (
|
||||
"incoming_rate"
|
||||
if self.get("is_internal_supplier") and not self.doctype == "Purchase Order"
|
||||
else "rate"
|
||||
)
|
||||
rate = flt(
|
||||
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
|
||||
* (d.conversion_factor or 1),
|
||||
|
||||
@@ -56,10 +56,24 @@ def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part
|
||||
|
||||
copy_attributes_to_variant(template, variant)
|
||||
|
||||
variant.manufacturer = manufacturer
|
||||
variant.manufacturer_part_no = manufacturer_part_no
|
||||
|
||||
variant.item_code = append_number_if_name_exists("Item", template.name)
|
||||
variant.flags.ignore_mandatory = True
|
||||
variant.save()
|
||||
|
||||
if not frappe.db.exists(
|
||||
"Item Manufacturer", {"item_code": variant.name, "manufacturer": manufacturer}
|
||||
):
|
||||
manufacturer_doc = frappe.new_doc("Item Manufacturer")
|
||||
manufacturer_doc.update(
|
||||
{
|
||||
"item_code": variant.name,
|
||||
"manufacturer": manufacturer,
|
||||
"manufacturer_part_no": manufacturer_part_no,
|
||||
}
|
||||
)
|
||||
|
||||
manufacturer_doc.flags.ignore_mandatory = True
|
||||
manufacturer_doc.save(ignore_permissions=True)
|
||||
|
||||
return variant
|
||||
|
||||
|
||||
@@ -896,3 +896,31 @@ def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len,
|
||||
as_list=1,
|
||||
)
|
||||
return terms
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_filtered_child_rows(doctype, txt, searchfield, start, page_len, filters) -> list:
|
||||
table = frappe.qb.DocType(doctype)
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(
|
||||
table.name,
|
||||
Concat("#", table.idx, ", ", table.item_code),
|
||||
)
|
||||
.orderby(table.idx)
|
||||
.offset(start)
|
||||
.limit(page_len)
|
||||
)
|
||||
|
||||
if filters:
|
||||
for field, value in filters.items():
|
||||
query = query.where(table[field] == value)
|
||||
|
||||
if txt:
|
||||
txt += "%"
|
||||
query = query.where(
|
||||
((table.idx.like(txt.replace("#", ""))) | (table.item_code.like(txt))) | (table.name.like(txt))
|
||||
)
|
||||
|
||||
return query.run(as_dict=False)
|
||||
|
||||
@@ -8,7 +8,9 @@ from frappe.model.meta import get_field_precision
|
||||
from frappe.utils import flt, format_datetime, get_datetime
|
||||
|
||||
import erpnext
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
|
||||
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
|
||||
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
|
||||
|
||||
|
||||
class StockOverReturnError(frappe.ValidationError):
|
||||
@@ -69,8 +71,6 @@ def validate_return_against(doc):
|
||||
|
||||
|
||||
def validate_returned_items(doc):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
valid_items = frappe._dict()
|
||||
|
||||
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
|
||||
@@ -116,33 +116,18 @@ def validate_returned_items(doc):
|
||||
ref = valid_items.get(d.item_code, frappe._dict())
|
||||
validate_quantity(doc, d, ref, valid_items, already_returned_items)
|
||||
|
||||
if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt(d.rate) > ref.rate:
|
||||
if (
|
||||
ref.rate
|
||||
and flt(d.rate) > ref.rate
|
||||
and doc.doctype in ("Delivery Note", "Sales Invoice")
|
||||
and get_valuation_method(ref.item_code) != "Moving Average"
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
|
||||
d.idx, doc.doctype, doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
elif ref.batch_no and d.batch_no not in ref.batch_no:
|
||||
frappe.throw(
|
||||
_("Row # {0}: Batch No must be same as {1} {2}").format(
|
||||
d.idx, doc.doctype, doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
elif ref.serial_no:
|
||||
if d.qty and not d.serial_no:
|
||||
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
|
||||
else:
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
for s in serial_nos:
|
||||
if s not in ref.serial_no:
|
||||
frappe.throw(
|
||||
_("Row # {0}: Serial No {1} does not match with {2} {3}").format(
|
||||
d.idx, s, doc.doctype, doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
warehouse_mandatory
|
||||
and not d.get("warehouse")
|
||||
@@ -397,71 +382,92 @@ def make_return_doc(
|
||||
else:
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
def update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
target_doc.qty = -1 * source_doc.qty
|
||||
item_details = frappe.get_cached_value(
|
||||
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
||||
)
|
||||
|
||||
returned_serial_nos = []
|
||||
if source_doc.get("serial_and_batch_bundle"):
|
||||
if item_details.has_serial_no:
|
||||
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
|
||||
returned_batches = frappe._dict()
|
||||
serial_and_batch_field = (
|
||||
"serial_and_batch_bundle" if qty_field == "stock_qty" else "rejected_serial_and_batch_bundle"
|
||||
)
|
||||
old_serial_no_field = "serial_no" if qty_field == "stock_qty" else "rejected_serial_no"
|
||||
old_batch_no_field = "batch_no"
|
||||
|
||||
type_of_transaction = "Inward"
|
||||
if (
|
||||
frappe.db.get_value(
|
||||
"Serial and Batch Bundle", source_doc.serial_and_batch_bundle, "type_of_transaction"
|
||||
)
|
||||
== "Inward"
|
||||
):
|
||||
type_of_transaction = "Outward"
|
||||
|
||||
cls_obj = SerialBatchCreation(
|
||||
{
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"serial_and_batch_bundle": source_doc.serial_and_batch_bundle,
|
||||
"returned_against": source_doc.name,
|
||||
"item_code": source_doc.item_code,
|
||||
"returned_serial_nos": returned_serial_nos,
|
||||
}
|
||||
)
|
||||
|
||||
cls_obj.duplicate_package()
|
||||
if cls_obj.serial_and_batch_bundle:
|
||||
target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
|
||||
|
||||
if source_doc.get("rejected_serial_and_batch_bundle"):
|
||||
if (
|
||||
source_doc.get(serial_and_batch_field)
|
||||
or source_doc.get(old_serial_no_field)
|
||||
or source_doc.get(old_batch_no_field)
|
||||
):
|
||||
if item_details.has_serial_no:
|
||||
returned_serial_nos = get_returned_serial_nos(
|
||||
source_doc, source_parent, serial_no_field="rejected_serial_and_batch_bundle"
|
||||
source_doc, source_parent, serial_no_field=serial_and_batch_field
|
||||
)
|
||||
else:
|
||||
returned_batches = get_returned_batches(
|
||||
source_doc, source_parent, batch_no_field=serial_and_batch_field
|
||||
)
|
||||
|
||||
type_of_transaction = "Inward"
|
||||
if (
|
||||
if source_doc.get(serial_and_batch_field) and (
|
||||
frappe.db.get_value(
|
||||
"Serial and Batch Bundle", source_doc.rejected_serial_and_batch_bundle, "type_of_transaction"
|
||||
"Serial and Batch Bundle", source_doc.get(serial_and_batch_field), "type_of_transaction"
|
||||
)
|
||||
== "Inward"
|
||||
):
|
||||
type_of_transaction = "Outward"
|
||||
elif source_parent.doctype in [
|
||||
"Purchase Invoice",
|
||||
"Purchase Receipt",
|
||||
"Subcontracting Receipt",
|
||||
]:
|
||||
type_of_transaction = "Outward"
|
||||
|
||||
cls_obj = SerialBatchCreation(
|
||||
{
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"serial_and_batch_bundle": source_doc.rejected_serial_and_batch_bundle,
|
||||
"serial_and_batch_bundle": source_doc.get(serial_and_batch_field),
|
||||
"returned_against": source_doc.name,
|
||||
"item_code": source_doc.item_code,
|
||||
"returned_serial_nos": returned_serial_nos,
|
||||
"voucher_type": source_parent.doctype,
|
||||
"do_not_submit": True,
|
||||
"warehouse": source_doc.warehouse,
|
||||
"has_serial_no": item_details.has_serial_no,
|
||||
"has_batch_no": item_details.has_batch_no,
|
||||
}
|
||||
)
|
||||
|
||||
cls_obj.duplicate_package()
|
||||
if cls_obj.serial_and_batch_bundle:
|
||||
target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
|
||||
serial_nos = []
|
||||
batches = frappe._dict()
|
||||
if source_doc.get(old_batch_no_field):
|
||||
batches = frappe._dict({source_doc.batch_no: source_doc.get(qty_field)})
|
||||
elif source_doc.get(old_serial_no_field):
|
||||
serial_nos = get_serial_nos(source_doc.get(old_serial_no_field))
|
||||
elif source_doc.get(serial_and_batch_field):
|
||||
if item_details.has_serial_no:
|
||||
serial_nos = get_serial_nos_from_bundle(source_doc.get(serial_and_batch_field))
|
||||
else:
|
||||
batches = get_batches_from_bundle(source_doc.get(serial_and_batch_field))
|
||||
|
||||
if serial_nos:
|
||||
cls_obj.serial_nos = sorted(list(set(serial_nos) - set(returned_serial_nos)))
|
||||
elif batches:
|
||||
for batch in batches:
|
||||
if batch in returned_batches:
|
||||
batches[batch] -= flt(returned_batches.get(batch))
|
||||
|
||||
cls_obj.batches = batches
|
||||
|
||||
if source_doc.get(serial_and_batch_field):
|
||||
cls_obj.duplicate_package()
|
||||
if cls_obj.serial_and_batch_bundle:
|
||||
target_doc.set(serial_and_batch_field, cls_obj.serial_and_batch_bundle)
|
||||
else:
|
||||
target_doc.set(serial_and_batch_field, cls_obj.make_serial_and_batch_bundle().name)
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1 * source_doc.qty
|
||||
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
||||
@@ -561,6 +567,18 @@ def make_return_doc(
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
|
||||
if source_doc.item_code:
|
||||
item_details = frappe.get_cached_value(
|
||||
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
||||
)
|
||||
|
||||
if not item_details.has_batch_no and not item_details.has_serial_no:
|
||||
return
|
||||
|
||||
for qty_field in ["stock_qty", "rejected_qty"]:
|
||||
if target_doc.get(qty_field):
|
||||
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
|
||||
|
||||
def update_terms(source_doc, target_doc, source_parent):
|
||||
target_doc.payment_amount = -source_doc.payment_amount
|
||||
|
||||
@@ -716,6 +734,9 @@ def get_returned_serial_nos(
|
||||
[parent_doc.doctype, "docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if serial_no_field == "rejected_serial_and_batch_bundle":
|
||||
filters.append([child_doc.doctype, "rejected_qty", ">", 0])
|
||||
|
||||
# Required for POS Invoice
|
||||
if ignore_voucher_detail_no:
|
||||
filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no])
|
||||
@@ -723,9 +744,57 @@ def get_returned_serial_nos(
|
||||
ids = []
|
||||
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
|
||||
ids.append(row.get("serial_and_batch_bundle"))
|
||||
if row.get(old_field):
|
||||
if row.get(old_field) and not row.get(serial_no_field):
|
||||
serial_nos.extend(get_serial_nos_from_serial_no(row.get(old_field)))
|
||||
|
||||
serial_nos.extend(get_serial_nos(ids))
|
||||
if ids:
|
||||
serial_nos.extend(get_serial_nos(ids))
|
||||
|
||||
return serial_nos
|
||||
|
||||
|
||||
def get_returned_batches(
|
||||
child_doc, parent_doc, batch_no_field=None, ignore_voucher_detail_no=None
|
||||
):
|
||||
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
|
||||
|
||||
batches = frappe._dict()
|
||||
|
||||
old_field = "batch_no"
|
||||
if not batch_no_field:
|
||||
batch_no_field = "serial_and_batch_bundle"
|
||||
|
||||
return_ref_field = frappe.scrub(child_doc.doctype)
|
||||
if child_doc.doctype == "Delivery Note Item":
|
||||
return_ref_field = "dn_detail"
|
||||
|
||||
fields = [
|
||||
f"`{'tab' + child_doc.doctype}`.`{batch_no_field}`",
|
||||
f"`{'tab' + child_doc.doctype}`.`batch_no`",
|
||||
f"`{'tab' + child_doc.doctype}`.`stock_qty`",
|
||||
]
|
||||
|
||||
filters = [
|
||||
[parent_doc.doctype, "return_against", "=", parent_doc.name],
|
||||
[parent_doc.doctype, "is_return", "=", 1],
|
||||
[child_doc.doctype, return_ref_field, "=", child_doc.name],
|
||||
[parent_doc.doctype, "docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if batch_no_field == "rejected_serial_and_batch_bundle":
|
||||
filters.append([child_doc.doctype, "rejected_qty", ">", 0])
|
||||
|
||||
# Required for POS Invoice
|
||||
if ignore_voucher_detail_no:
|
||||
filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no])
|
||||
|
||||
ids = []
|
||||
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
|
||||
ids.append(row.get("serial_and_batch_bundle"))
|
||||
if row.get(old_field) and not row.get(batch_no_field):
|
||||
batches.setdefault(row.get(old_field), row.get("stock_qty"))
|
||||
|
||||
if ids:
|
||||
batches.update(get_batches_from_bundle(ids))
|
||||
|
||||
return batches
|
||||
|
||||
@@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.stock.doctype.item.item import set_item_default
|
||||
from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
|
||||
|
||||
|
||||
class SellingController(StockController):
|
||||
@@ -295,9 +295,6 @@ class SellingController(StockController):
|
||||
def get_item_list(self):
|
||||
il = []
|
||||
for d in self.get("items"):
|
||||
if d.qty is None:
|
||||
frappe.throw(_("Row {0}: Qty is mandatory").format(d.idx))
|
||||
|
||||
if self.has_product_bundle(d.item_code):
|
||||
for p in self.get("packed_items"):
|
||||
if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
|
||||
@@ -308,6 +305,8 @@ class SellingController(StockController):
|
||||
"warehouse": p.warehouse or d.warehouse,
|
||||
"item_code": p.item_code,
|
||||
"qty": flt(p.qty),
|
||||
"serial_no": p.serial_no if self.docstatus == 2 else None,
|
||||
"batch_no": p.batch_no if self.docstatus == 2 else None,
|
||||
"uom": p.uom,
|
||||
"serial_and_batch_bundle": p.serial_and_batch_bundle
|
||||
or get_serial_and_batch_bundle(p, self),
|
||||
@@ -330,6 +329,8 @@ class SellingController(StockController):
|
||||
"warehouse": d.warehouse,
|
||||
"item_code": d.item_code,
|
||||
"qty": d.stock_qty,
|
||||
"serial_no": d.serial_no if self.docstatus == 2 else None,
|
||||
"batch_no": d.batch_no if self.docstatus == 2 else None,
|
||||
"uom": d.uom,
|
||||
"stock_uom": d.stock_uom,
|
||||
"conversion_factor": d.conversion_factor,
|
||||
@@ -428,11 +429,18 @@ class SellingController(StockController):
|
||||
|
||||
items = self.get("items") + (self.get("packed_items") or [])
|
||||
for d in items:
|
||||
if not self.get("return_against"):
|
||||
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||
continue
|
||||
|
||||
if not self.get("return_against") or (
|
||||
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
|
||||
):
|
||||
# Get incoming rate based on original item cost based on valuation method
|
||||
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
|
||||
|
||||
if not (self.get("is_return") and d.incoming_rate):
|
||||
if not d.incoming_rate or (
|
||||
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
|
||||
):
|
||||
d.incoming_rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": d.item_code,
|
||||
|
||||
@@ -131,11 +131,6 @@ status_map = {
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
|
||||
],
|
||||
],
|
||||
"Bank Transaction": [
|
||||
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
|
||||
["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"],
|
||||
["Cancelled", "eval:self.docstatus == 2"],
|
||||
],
|
||||
"POS Opening Entry": [
|
||||
["Draft", None],
|
||||
["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"],
|
||||
|
||||
@@ -162,7 +162,9 @@ class StockController(AccountsController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_account[sle.warehouse]["account"],
|
||||
"against_type": "Account",
|
||||
"against": expense_account,
|
||||
"against_link": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get("project"),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
@@ -178,7 +180,9 @@ class StockController(AccountsController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against_type": "Account",
|
||||
"against": warehouse_account[sle.warehouse]["account"],
|
||||
"against_link": warehouse_account[sle.warehouse]["account"],
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(sle.stock_value_difference, precision),
|
||||
@@ -210,7 +214,9 @@ class StockController(AccountsController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against_type": "Account",
|
||||
"against": warehouse_asset_account,
|
||||
"against_link": warehouse_asset_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get("project"),
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
@@ -226,7 +232,9 @@ class StockController(AccountsController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_asset_account,
|
||||
"against_type": "Account",
|
||||
"against": expense_account,
|
||||
"against_link": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"credit": sle_rounding_diff,
|
||||
@@ -455,6 +463,12 @@ class StockController(AccountsController):
|
||||
sl_dict.update(args)
|
||||
self.update_inventory_dimensions(d, sl_dict)
|
||||
|
||||
if self.docstatus == 2:
|
||||
# To handle denormalized serial no records, will br deprecated in v16
|
||||
for field in ["serial_no", "batch_no"]:
|
||||
if d.get(field):
|
||||
sl_dict[field] = d.get(field)
|
||||
|
||||
return sl_dict
|
||||
|
||||
def update_inventory_dimensions(self, row, sl_dict) -> None:
|
||||
@@ -826,6 +840,7 @@ class StockController(AccountsController):
|
||||
credit,
|
||||
remarks,
|
||||
against_account,
|
||||
against_type="Account",
|
||||
debit_in_account_currency=None,
|
||||
credit_in_account_currency=None,
|
||||
account_currency=None,
|
||||
@@ -840,7 +855,9 @@ class StockController(AccountsController):
|
||||
"cost_center": cost_center,
|
||||
"debit": debit,
|
||||
"credit": credit,
|
||||
"against_type": against_type,
|
||||
"against": against_account,
|
||||
"against_link": against_account,
|
||||
"remarks": remarks,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user