Merge branch 'develop' into stock-reservation
This commit is contained in:
commit
81a57e4e41
@ -53,19 +53,20 @@ class BankStatementImport(DataImport):
|
||||
if "Bank Account" not in json.dumps(preview["columns"]):
|
||||
frappe.throw(_("Please add the Bank Account column"))
|
||||
|
||||
from frappe.utils.background_jobs import is_job_queued
|
||||
from frappe.utils.background_jobs import is_job_enqueued
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||
|
||||
if not is_job_queued(self.name):
|
||||
job_id = f"bank_statement_import::{self.name}"
|
||||
if not is_job_enqueued(job_id):
|
||||
enqueue(
|
||||
start_import,
|
||||
queue="default",
|
||||
timeout=6000,
|
||||
event="data_import",
|
||||
job_name=self.name,
|
||||
job_id=job_id,
|
||||
data_import=self.name,
|
||||
bank_account=self.bank_account,
|
||||
import_file_path=self.import_file,
|
||||
|
@ -4,7 +4,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.background_jobs import is_job_queued
|
||||
from frappe.utils.background_jobs import is_job_enqueued
|
||||
|
||||
from erpnext.accounts.doctype.account.account import merge_account
|
||||
|
||||
@ -17,13 +17,14 @@ class LedgerMerge(Document):
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
|
||||
|
||||
if not is_job_queued(self.name):
|
||||
job_id = f"ledger_merge::{self.name}"
|
||||
if not is_job_enqueued(job_id):
|
||||
enqueue(
|
||||
start_merge,
|
||||
queue="default",
|
||||
timeout=6000,
|
||||
event="ledger_merge",
|
||||
job_name=self.name,
|
||||
job_id=job_id,
|
||||
docname=self.name,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe.utils.background_jobs import enqueue, is_job_queued
|
||||
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
@ -212,13 +212,15 @@ class OpeningInvoiceCreationTool(Document):
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||
|
||||
if not is_job_queued(self.name):
|
||||
job_id = f"opening_invoice::{self.name}"
|
||||
|
||||
if not is_job_enqueued(job_id):
|
||||
enqueue(
|
||||
start_import,
|
||||
queue="default",
|
||||
timeout=6000,
|
||||
event="opening_invoice_creation",
|
||||
job_name=self.name,
|
||||
job_id=job_id,
|
||||
invoices=invoices,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc, map_doc
|
||||
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
||||
from frappe.utils.background_jobs import enqueue, is_job_queued
|
||||
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
|
||||
@ -483,15 +483,15 @@ def enqueue_job(job, **kwargs):
|
||||
|
||||
closing_entry = kwargs.get("closing_entry") or {}
|
||||
|
||||
job_name = closing_entry.get("name")
|
||||
if not is_job_queued(job_name):
|
||||
job_id = "pos_invoice_merge::" + str(closing_entry.get("name"))
|
||||
if not is_job_enqueued(job_id):
|
||||
enqueue(
|
||||
job,
|
||||
**kwargs,
|
||||
queue="long",
|
||||
timeout=10000,
|
||||
event="processing_merge_logs",
|
||||
job_name=job_name,
|
||||
job_id=job_id,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||
)
|
||||
|
||||
|
@ -164,7 +164,7 @@ def trigger_reconciliation_for_queued_docs():
|
||||
Fetch queued docs and start reconciliation process for each one
|
||||
"""
|
||||
if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
|
||||
frappe.throw(
|
||||
frappe.msgprint(
|
||||
_("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format(
|
||||
get_link_to_form("Accounts Settings", "Accounts Settings")
|
||||
)
|
||||
|
@ -546,12 +546,13 @@ def apply_additional_conditions(doctype, query, from_date, ignore_closing_entrie
|
||||
)
|
||||
|
||||
query = query.where(
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb)]))
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
| (gl_entry.finance_book.isnull())
|
||||
)
|
||||
else:
|
||||
query = query.where(
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book)])) | (gl_entry.finance_book.isnull())
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), ""]))
|
||||
| (gl_entry.finance_book.isnull())
|
||||
)
|
||||
|
||||
if accounting_dimensions:
|
||||
|
@ -253,14 +253,14 @@ def get_conditions(filters):
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
|
||||
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)")
|
||||
conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
if filters.get("finance_book"):
|
||||
conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
|
||||
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
conditions.append("(finance_book IS NULL)")
|
||||
conditions.append("(finance_book in ('') OR finance_book IS NULL)")
|
||||
|
||||
if not filters.get("show_cancelled_entries"):
|
||||
conditions.append("is_cancelled = 0")
|
||||
|
@ -256,12 +256,12 @@ def get_opening_balance(
|
||||
)
|
||||
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb)]))
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
| (closing_balance.finance_book.isnull())
|
||||
)
|
||||
else:
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book)]))
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
|
||||
| (closing_balance.finance_book.isnull())
|
||||
)
|
||||
|
||||
|
@ -442,7 +442,29 @@ class StockController(AccountsController):
|
||||
if not dimension:
|
||||
continue
|
||||
|
||||
if row.get(dimension.source_fieldname):
|
||||
if self.doctype in [
|
||||
"Purchase Invoice",
|
||||
"Purchase Receipt",
|
||||
"Sales Invoice",
|
||||
"Delivery Note",
|
||||
"Stock Entry",
|
||||
]:
|
||||
if (sl_dict.actual_qty > 0 and self.doctype in ["Purchase Invoice", "Purchase Receipt"]) or (
|
||||
sl_dict.actual_qty < 0 and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"]
|
||||
):
|
||||
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||
else:
|
||||
fieldname_start_with = "to"
|
||||
if self.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
fieldname_start_with = "from"
|
||||
|
||||
fieldname = f"{fieldname_start_with}_{dimension.source_fieldname}"
|
||||
sl_dict[dimension.target_fieldname] = row.get(fieldname)
|
||||
|
||||
if not sl_dict.get(dimension.target_fieldname):
|
||||
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||
|
||||
elif row.get(dimension.source_fieldname):
|
||||
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||
|
||||
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
|
||||
|
@ -165,6 +165,7 @@
|
||||
"fieldname": "slide_3_content_align",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Align",
|
||||
"options": "Left\nCentre\nRight",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
@ -214,6 +215,7 @@
|
||||
"fieldname": "slide_4_content_align",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Align",
|
||||
"options": "Left\nCentre\nRight",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
@ -263,6 +265,7 @@
|
||||
"fieldname": "slide_5_content_align",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Align",
|
||||
"options": "Left\nCentre\nRight",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
@ -274,7 +277,7 @@
|
||||
}
|
||||
],
|
||||
"idx": 2,
|
||||
"modified": "2021-02-24 15:57:05.889709",
|
||||
"modified": "2023-05-12 15:03:57.604060",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "Hero Slider",
|
||||
|
@ -48,7 +48,8 @@ frappe.ui.form.on("BOM", {
|
||||
return {
|
||||
query: "erpnext.manufacturing.doctype.bom.bom.item_query",
|
||||
filters: {
|
||||
"item_code": doc.item
|
||||
"include_item_in_manufacturing": 1,
|
||||
"is_fixed_asset": 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -1339,8 +1339,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not has_variants:
|
||||
query_filters["has_variants"] = 0
|
||||
|
||||
if filters and filters.get("is_stock_item"):
|
||||
query_filters["is_stock_item"] = 1
|
||||
if filters:
|
||||
for fieldname, value in filters.items():
|
||||
query_filters[fieldname] = value
|
||||
|
||||
return frappe.get_list(
|
||||
"Item",
|
||||
|
@ -698,6 +698,45 @@ class TestBOM(FrappeTestCase):
|
||||
bom.update_cost()
|
||||
self.assertFalse(bom.flags.cost_updated)
|
||||
|
||||
def test_do_not_include_manufacturing_and_fixed_items(self):
|
||||
from erpnext.manufacturing.doctype.bom.bom import item_query
|
||||
|
||||
if not frappe.db.exists("Asset Category", "Computers-Test"):
|
||||
doc = frappe.get_doc({"doctype": "Asset Category", "asset_category_name": "Computers-Test"})
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.insert()
|
||||
|
||||
for item_code, properties in {
|
||||
"_Test RM Item 1 Do Not Include In Manufacture": {
|
||||
"is_stock_item": 1,
|
||||
"include_item_in_manufacturing": 0,
|
||||
},
|
||||
"_Test RM Item 2 Fixed Asset Item": {
|
||||
"is_fixed_asset": 1,
|
||||
"is_stock_item": 0,
|
||||
"asset_category": "Computers-Test",
|
||||
},
|
||||
"_Test RM Item 3 Manufacture Item": {"is_stock_item": 1, "include_item_in_manufacturing": 1},
|
||||
}.items():
|
||||
make_item(item_code, properties)
|
||||
|
||||
data = item_query(
|
||||
"Item",
|
||||
txt="_Test RM Item",
|
||||
searchfield="name",
|
||||
start=0,
|
||||
page_len=20000,
|
||||
filters={"include_item_in_manufacturing": 1, "is_fixed_asset": 0},
|
||||
)
|
||||
|
||||
items = []
|
||||
for row in data:
|
||||
items.append(row[0])
|
||||
|
||||
self.assertTrue("_Test RM Item 1 Do Not Include In Manufacture" not in items)
|
||||
self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
|
||||
self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
|
||||
|
||||
|
||||
def get_default_bom(item_code="_Test FG Item 2"):
|
||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||
|
@ -326,7 +326,7 @@ erpnext.patches.v13_0.update_docs_link
|
||||
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
||||
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
||||
erpnext.patches.v14_0.update_closing_balances
|
||||
erpnext.patches.v14_0.update_closing_balances #10-05-2023
|
||||
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||
# below migration patches should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
|
@ -11,6 +11,8 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.db.truncate("Account Closing Balance")
|
||||
|
||||
company_wise_order = {}
|
||||
get_opening_entries = True
|
||||
for pcv in frappe.db.get_all(
|
||||
@ -35,7 +37,20 @@ def execute():
|
||||
entry["closing_date"] = pcv_doc.posting_date
|
||||
entry["period_closing_voucher"] = pcv_doc.name
|
||||
|
||||
closing_entries = pcv_doc.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||
closing_entries = frappe.db.get_all(
|
||||
"GL Entry",
|
||||
filters={
|
||||
"is_cancelled": 0,
|
||||
"voucher_no": ["!=", pcv.name],
|
||||
"posting_date": ["<=", pcv.posting_date],
|
||||
},
|
||||
fields=["*"],
|
||||
)
|
||||
|
||||
for entry in closing_entries:
|
||||
entry["closing_date"] = pcv_doc.posting_date
|
||||
entry["period_closing_voucher"] = pcv_doc.name
|
||||
|
||||
make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
|
||||
company_wise_order[pcv.company].append(pcv.posting_date)
|
||||
get_opening_entries = False
|
||||
|
@ -92,7 +92,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
_calculate_taxes_and_totals() {
|
||||
const is_quotation = this.frm.doc.doctype == "Quotation";
|
||||
this.frm.doc._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
|
||||
this.frm._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
|
||||
|
||||
this.validate_conversion_rate();
|
||||
this.calculate_item_values();
|
||||
@ -125,7 +125,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
calculate_item_values() {
|
||||
var me = this;
|
||||
if (!this.discount_amount_applied) {
|
||||
for (const item of this.frm.doc._items || []) {
|
||||
for (const item of this.frm._items || []) {
|
||||
frappe.model.round_floats_in(item);
|
||||
item.net_rate = item.rate;
|
||||
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
|
||||
@ -209,7 +209,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
});
|
||||
if(has_inclusive_tax==false) return;
|
||||
|
||||
$.each(me.frm.doc._items || [], function(n, item) {
|
||||
$.each(me.frm._items || [], function(n, item) {
|
||||
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
||||
var cumulated_tax_fraction = 0.0;
|
||||
var total_inclusive_tax_amount_per_qty = 0;
|
||||
@ -280,13 +280,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
var me = this;
|
||||
this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0;
|
||||
|
||||
$.each(this.frm.doc._items || [], function(i, item) {
|
||||
$.each(this.frm._items || [], function(i, item) {
|
||||
me.frm.doc.total += item.amount;
|
||||
me.frm.doc.total_qty += item.qty;
|
||||
me.frm.doc.base_total += item.base_amount;
|
||||
me.frm.doc.net_total += item.net_amount;
|
||||
me.frm.doc.base_net_total += item.base_net_amount;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
calculate_shipping_charges() {
|
||||
@ -333,7 +333,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
});
|
||||
|
||||
$.each(this.frm.doc._items || [], function(n, item) {
|
||||
$.each(this.frm._items || [], function(n, item) {
|
||||
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
||||
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
||||
// tax_amount represents the amount of tax for the current step
|
||||
@ -342,7 +342,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
// Adjust divisional loss to the last item
|
||||
if (tax.charge_type == "Actual") {
|
||||
actual_tax_dict[tax.idx] -= current_tax_amount;
|
||||
if (n == me.frm.doc._items.length - 1) {
|
||||
if (n == me.frm._items.length - 1) {
|
||||
current_tax_amount += actual_tax_dict[tax.idx];
|
||||
}
|
||||
}
|
||||
@ -379,7 +379,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
// set precision in the last item iteration
|
||||
if (n == me.frm.doc._items.length - 1) {
|
||||
if (n == me.frm._items.length - 1) {
|
||||
me.round_off_totals(tax);
|
||||
me.set_in_company_currency(tax,
|
||||
["tax_amount", "tax_amount_after_discount_amount"]);
|
||||
@ -602,7 +602,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
_cleanup() {
|
||||
this.frm.doc.base_in_words = this.frm.doc.in_words = "";
|
||||
let items = this.frm.doc._items;
|
||||
let items = this.frm._items;
|
||||
|
||||
if(items && items.length) {
|
||||
if(!frappe.meta.get_docfield(items[0].doctype, "item_tax_amount", this.frm.doctype)) {
|
||||
@ -659,7 +659,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
var net_total = 0;
|
||||
// calculate item amount after Discount Amount
|
||||
if (total_for_discount_amount) {
|
||||
$.each(this.frm.doc._items || [], function(i, item) {
|
||||
$.each(this.frm._items || [], function(i, item) {
|
||||
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
|
||||
item.net_amount = flt(item.net_amount - distributed_amount,
|
||||
precision("base_amount", item));
|
||||
@ -667,7 +667,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
// discount amount rounding loss adjustment if no taxes
|
||||
if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total"))
|
||||
&& i == (me.frm.doc._items || []).length - 1) {
|
||||
&& i == (me.frm._items || []).length - 1) {
|
||||
var discount_amount_loss = flt(me.frm.doc.net_total - net_total
|
||||
- me.frm.doc.discount_amount, precision("net_total"));
|
||||
item.net_amount = flt(item.net_amount + discount_amount_loss,
|
||||
|
@ -797,6 +797,8 @@ def make_project(source_name, target_doc=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("set_po_nos")
|
||||
@ -811,6 +813,8 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
||||
if target.company_address:
|
||||
target.update(get_fetch_values("Delivery Note", "company_address", target.company_address))
|
||||
|
||||
make_packing_list(target)
|
||||
|
||||
def update_item(source, target, source_parent):
|
||||
target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
|
||||
target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
|
||||
|
@ -2042,6 +2042,75 @@ class TestSalesOrder(FrappeTestCase):
|
||||
|
||||
self.assertEqual(mr.items[0].qty, 6)
|
||||
|
||||
def test_packed_items_for_partial_sales_order(self):
|
||||
# test Update Items with product bundle
|
||||
for product_bundle in [
|
||||
"_Test Product Bundle Item Partial 1",
|
||||
"_Test Product Bundle Item Partial 2",
|
||||
]:
|
||||
if not frappe.db.exists("Item", product_bundle):
|
||||
bundle_item = make_item(product_bundle, {"is_stock_item": 0})
|
||||
bundle_item.append(
|
||||
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
|
||||
)
|
||||
bundle_item.save(ignore_permissions=True)
|
||||
|
||||
for product_bundle in ["_Packed Item Partial 1", "_Packed Item Partial 2"]:
|
||||
if not frappe.db.exists("Item", product_bundle):
|
||||
make_item(product_bundle, {"is_stock_item": 1, "stock_uom": "Nos"})
|
||||
|
||||
make_stock_entry(item=product_bundle, target="_Test Warehouse - _TC", qty=2, rate=10)
|
||||
|
||||
make_product_bundle("_Test Product Bundle Item Partial 1", ["_Packed Item Partial 1"], 1)
|
||||
|
||||
make_product_bundle("_Test Product Bundle Item Partial 2", ["_Packed Item Partial 2"], 1)
|
||||
|
||||
so = make_sales_order(
|
||||
item_code="_Test Product Bundle Item Partial 1",
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
qty=1,
|
||||
uom="Nos",
|
||||
stock_uom="Nos",
|
||||
conversion_factor=1,
|
||||
transaction_date=nowdate(),
|
||||
delivery_note=nowdate(),
|
||||
do_not_submit=1,
|
||||
)
|
||||
|
||||
so.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Product Bundle Item Partial 2",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 1,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"delivery_note": nowdate(),
|
||||
},
|
||||
)
|
||||
|
||||
so.save()
|
||||
so.submit()
|
||||
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.remove(dn.items[1])
|
||||
dn.save()
|
||||
dn.submit()
|
||||
|
||||
self.assertEqual(len(dn.items), 1)
|
||||
self.assertEqual(len(dn.packed_items), 1)
|
||||
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 1")
|
||||
|
||||
so.load_from_db()
|
||||
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.save()
|
||||
|
||||
self.assertEqual(len(dn.items), 1)
|
||||
self.assertEqual(len(dn.packed_items), 1)
|
||||
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2")
|
||||
|
||||
|
||||
def automatically_fetch_payment_terms(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
|
@ -75,7 +75,16 @@ class InventoryDimension(Document):
|
||||
self.delete_custom_fields()
|
||||
|
||||
def delete_custom_fields(self):
|
||||
filters = {"fieldname": self.source_fieldname}
|
||||
filters = {
|
||||
"fieldname": (
|
||||
"in",
|
||||
[
|
||||
self.source_fieldname,
|
||||
f"to_{self.source_fieldname}",
|
||||
f"from_{self.source_fieldname}",
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
if self.document_type:
|
||||
filters["dt"] = self.document_type
|
||||
@ -88,6 +97,8 @@ class InventoryDimension(Document):
|
||||
|
||||
def reset_value(self):
|
||||
if self.apply_to_all_doctypes:
|
||||
self.type_of_transaction = ""
|
||||
|
||||
self.istable = 0
|
||||
for field in ["document_type", "condition"]:
|
||||
self.set(field, None)
|
||||
@ -111,12 +122,35 @@ class InventoryDimension(Document):
|
||||
def on_update(self):
|
||||
self.add_custom_fields()
|
||||
|
||||
def add_custom_fields(self):
|
||||
dimension_fields = [
|
||||
@staticmethod
|
||||
def get_insert_after_fieldname(doctype):
|
||||
return frappe.get_all(
|
||||
"DocField",
|
||||
fields=["fieldname"],
|
||||
filters={"parent": doctype},
|
||||
order_by="idx desc",
|
||||
limit=1,
|
||||
)[0].fieldname
|
||||
|
||||
def get_dimension_fields(self, doctype=None):
|
||||
if not doctype:
|
||||
doctype = self.document_type
|
||||
|
||||
label_start_with = ""
|
||||
if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
|
||||
label_start_with = "Target"
|
||||
elif doctype in ["Sales Invoice Item", "Delivery Note Item", "Stock Entry Detail"]:
|
||||
label_start_with = "Source"
|
||||
|
||||
label = self.dimension_name
|
||||
if label_start_with:
|
||||
label = f"{label_start_with} {self.dimension_name}"
|
||||
|
||||
return [
|
||||
dict(
|
||||
fieldname="inventory_dimension",
|
||||
fieldtype="Section Break",
|
||||
insert_after="warehouse",
|
||||
insert_after=self.get_insert_after_fieldname(doctype),
|
||||
label="Inventory Dimension",
|
||||
collapsible=1,
|
||||
),
|
||||
@ -125,24 +159,37 @@ class InventoryDimension(Document):
|
||||
fieldtype="Link",
|
||||
insert_after="inventory_dimension",
|
||||
options=self.reference_document,
|
||||
label=self.dimension_name,
|
||||
label=label,
|
||||
reqd=self.reqd,
|
||||
mandatory_depends_on=self.mandatory_depends_on,
|
||||
),
|
||||
]
|
||||
|
||||
def add_custom_fields(self):
|
||||
custom_fields = {}
|
||||
|
||||
dimension_fields = []
|
||||
if self.apply_to_all_doctypes:
|
||||
for doctype in get_inventory_documents():
|
||||
if not field_exists(doctype[0], self.source_fieldname):
|
||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||
if field_exists(doctype[0], self.source_fieldname):
|
||||
continue
|
||||
|
||||
dimension_fields = self.get_dimension_fields(doctype[0])
|
||||
self.add_transfer_field(doctype[0], dimension_fields)
|
||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||
elif not field_exists(self.document_type, self.source_fieldname):
|
||||
dimension_fields = self.get_dimension_fields()
|
||||
|
||||
self.add_transfer_field(self.document_type, dimension_fields)
|
||||
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):
|
||||
if (
|
||||
dimension_fields
|
||||
and 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["mandatory_depends_on"] = ""
|
||||
dimension_field["reqd"] = 0
|
||||
@ -152,6 +199,53 @@ class InventoryDimension(Document):
|
||||
if custom_fields:
|
||||
create_custom_fields(custom_fields)
|
||||
|
||||
def add_transfer_field(self, doctype, dimension_fields):
|
||||
if doctype not in [
|
||||
"Stock Entry Detail",
|
||||
"Sales Invoice Item",
|
||||
"Delivery Note Item",
|
||||
"Purchase Invoice Item",
|
||||
"Purchase Receipt Item",
|
||||
]:
|
||||
return
|
||||
|
||||
fieldname_start_with = "to"
|
||||
label_start_with = "Target"
|
||||
display_depends_on = ""
|
||||
|
||||
if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
|
||||
fieldname_start_with = "from"
|
||||
label_start_with = "Source"
|
||||
display_depends_on = "eval:parent.is_internal_supplier == 1"
|
||||
elif doctype != "Stock Entry Detail":
|
||||
display_depends_on = "eval:parent.is_internal_customer == 1"
|
||||
elif doctype == "Stock Entry Detail":
|
||||
display_depends_on = "eval:parent.purpose != 'Material Issue'"
|
||||
|
||||
fieldname = f"{fieldname_start_with}_{self.source_fieldname}"
|
||||
label = f"{label_start_with} {self.dimension_name}"
|
||||
|
||||
if field_exists(doctype, fieldname):
|
||||
return
|
||||
|
||||
dimension_fields.extend(
|
||||
[
|
||||
dict(
|
||||
fieldname="inventory_dimension_col_break",
|
||||
fieldtype="Column Break",
|
||||
insert_after=self.source_fieldname,
|
||||
),
|
||||
dict(
|
||||
fieldname=fieldname,
|
||||
fieldtype="Link",
|
||||
insert_after="inventory_dimension_col_break",
|
||||
options=self.reference_document,
|
||||
label=label,
|
||||
depends_on=display_depends_on,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def field_exists(doctype, fieldname) -> str or None:
|
||||
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
|
||||
@ -185,18 +279,19 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
|
||||
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
|
||||
filter_dimensions = []
|
||||
for row in dimensions:
|
||||
if (
|
||||
row.type_of_transaction == "Inward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Inward"
|
||||
) and sl_dict.actual_qty < 0:
|
||||
continue
|
||||
elif (
|
||||
row.type_of_transaction == "Outward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Outward"
|
||||
) and sl_dict.actual_qty > 0:
|
||||
continue
|
||||
if row.type_of_transaction:
|
||||
if (
|
||||
row.type_of_transaction == "Inward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Inward"
|
||||
) and sl_dict.actual_qty < 0:
|
||||
continue
|
||||
elif (
|
||||
row.type_of_transaction == "Outward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Outward"
|
||||
) and sl_dict.actual_qty > 0:
|
||||
continue
|
||||
|
||||
evals = {"doc": doc}
|
||||
if parent_doc:
|
||||
|
@ -12,6 +12,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
||||
DoNotChangeError,
|
||||
delete_dimension,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
@ -20,6 +21,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
class TestInventoryDimension(FrappeTestCase):
|
||||
def setUp(self):
|
||||
prepare_test_data()
|
||||
create_store_dimension()
|
||||
|
||||
def test_validate_inventory_dimension(self):
|
||||
# Can not be child doc
|
||||
@ -73,6 +75,8 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
self.assertFalse(custom_field)
|
||||
|
||||
def test_inventory_dimension(self):
|
||||
frappe.local.document_wise_inventory_dimensions = {}
|
||||
|
||||
warehouse = "Shelf Warehouse - _TC"
|
||||
item_code = "_Test Item"
|
||||
|
||||
@ -143,6 +147,8 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
||||
|
||||
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
|
||||
frappe.local.document_wise_inventory_dimensions = {}
|
||||
|
||||
inv_dimension = create_inventory_dimension(
|
||||
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
|
||||
)
|
||||
@ -250,6 +256,97 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
def test_for_purchase_sales_and_stock_transaction(self):
|
||||
create_inventory_dimension(
|
||||
reference_document="Store",
|
||||
type_of_transaction="Outward",
|
||||
dimension_name="Store",
|
||||
apply_to_all_doctypes=1,
|
||||
)
|
||||
|
||||
item_code = "Test Inventory Dimension Item"
|
||||
create_item(item_code)
|
||||
warehouse = create_warehouse("Store Warehouse")
|
||||
|
||||
# Purchase Receipt -> Inward in Store 1
|
||||
pr_doc = make_purchase_receipt(
|
||||
item_code=item_code, warehouse=warehouse, qty=10, rate=100, do_not_submit=True
|
||||
)
|
||||
|
||||
pr_doc.items[0].store = "Store 1"
|
||||
pr_doc.save()
|
||||
pr_doc.submit()
|
||||
|
||||
entries = get_voucher_sl_entries(pr_doc.name, ["warehouse", "store", "incoming_rate"])
|
||||
|
||||
self.assertEqual(entries[0].warehouse, warehouse)
|
||||
self.assertEqual(entries[0].store, "Store 1")
|
||||
|
||||
# Stock Entry -> Transfer from Store 1 to Store 2
|
||||
se_doc = make_stock_entry(
|
||||
item_code=item_code, qty=10, from_warehouse=warehouse, to_warehouse=warehouse, do_not_save=True
|
||||
)
|
||||
|
||||
se_doc.items[0].store = "Store 1"
|
||||
se_doc.items[0].to_store = "Store 2"
|
||||
|
||||
se_doc.save()
|
||||
se_doc.submit()
|
||||
|
||||
entries = get_voucher_sl_entries(
|
||||
se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"]
|
||||
)
|
||||
|
||||
for entry in entries:
|
||||
self.assertEqual(entry.warehouse, warehouse)
|
||||
if entry.actual_qty > 0:
|
||||
self.assertEqual(entry.store, "Store 2")
|
||||
self.assertEqual(entry.incoming_rate, 100.0)
|
||||
else:
|
||||
self.assertEqual(entry.store, "Store 1")
|
||||
|
||||
# Delivery Note -> Outward from Store 2
|
||||
|
||||
dn_doc = create_delivery_note(item_code=item_code, qty=10, warehouse=warehouse, do_not_save=True)
|
||||
|
||||
dn_doc.items[0].store = "Store 2"
|
||||
dn_doc.save()
|
||||
dn_doc.submit()
|
||||
|
||||
entries = get_voucher_sl_entries(dn_doc.name, ["warehouse", "store", "actual_qty"])
|
||||
|
||||
self.assertEqual(entries[0].warehouse, warehouse)
|
||||
self.assertEqual(entries[0].store, "Store 2")
|
||||
self.assertEqual(entries[0].actual_qty, -10.0)
|
||||
|
||||
|
||||
def get_voucher_sl_entries(voucher_no, fields):
|
||||
return frappe.get_all(
|
||||
"Stock Ledger Entry", filters={"voucher_no": voucher_no}, fields=fields, order_by="creation"
|
||||
)
|
||||
|
||||
|
||||
def create_store_dimension():
|
||||
if not frappe.db.exists("DocType", "Store"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "DocType",
|
||||
"name": "Store",
|
||||
"module": "Stock",
|
||||
"custom": 1,
|
||||
"naming_rule": "By fieldname",
|
||||
"autoname": "field:store_name",
|
||||
"fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}],
|
||||
"permissions": [
|
||||
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
for store in ["Store 1", "Store 2"]:
|
||||
if not frappe.db.exists("Store", store):
|
||||
frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True)
|
||||
|
||||
|
||||
def prepare_test_data():
|
||||
if not frappe.db.exists("DocType", "Shelf"):
|
||||
|
@ -9,7 +9,17 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
comma_or,
|
||||
cstr,
|
||||
flt,
|
||||
format_time,
|
||||
formatdate,
|
||||
getdate,
|
||||
month_diff,
|
||||
nowdate,
|
||||
)
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
@ -151,6 +161,41 @@ class StockEntry(StockController):
|
||||
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
|
||||
def submit(self):
|
||||
if self.is_enqueue_action():
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"
|
||||
)
|
||||
)
|
||||
self.queue_action("submit", timeout=2000)
|
||||
else:
|
||||
self._submit()
|
||||
|
||||
def cancel(self):
|
||||
if self.is_enqueue_action():
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"
|
||||
)
|
||||
)
|
||||
self.queue_action("cancel", timeout=2000)
|
||||
else:
|
||||
self._cancel()
|
||||
|
||||
def is_enqueue_action(self, force=False) -> bool:
|
||||
if force:
|
||||
return True
|
||||
|
||||
if frappe.flags.in_test:
|
||||
return False
|
||||
|
||||
# If line items are more than 100 or record is older than 6 months
|
||||
if len(self.items) > 100 or month_diff(nowdate(), self.posting_date) > 6:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe.permissions import add_user_permission, remove_user_permission
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, nowdate, nowtime, today
|
||||
from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.stock.doctype.item.test_item import (
|
||||
@ -1707,6 +1707,36 @@ class TestStockEntry(FrappeTestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, sr_doc.submit)
|
||||
|
||||
def test_enqueue_action(self):
|
||||
frappe.flags.in_test = False
|
||||
item_code = "Test Enqueue Item - 001"
|
||||
create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
|
||||
|
||||
doc = make_stock_entry(
|
||||
item_code=item_code,
|
||||
posting_date=add_to_date(today(), months=-7),
|
||||
posting_time="00:00:00",
|
||||
purpose="Material Receipt",
|
||||
qty=10,
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertTrue(doc.is_enqueue_action())
|
||||
|
||||
doc = make_stock_entry(
|
||||
item_code=item_code,
|
||||
posting_date=today(),
|
||||
posting_time="00:00:00",
|
||||
purpose="Material Receipt",
|
||||
qty=10,
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertFalse(doc.is_enqueue_action())
|
||||
frappe.flags.in_test = True
|
||||
|
||||
|
||||
def make_serialized_item(**args):
|
||||
args = frappe._dict(args)
|
||||
|
@ -395,7 +395,8 @@
|
||||
"no_copy": 1,
|
||||
"options": "Material Request",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request_item",
|
||||
@ -571,7 +572,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-01-03 14:51:16.575515",
|
||||
"modified": "2023-05-09 12:41:18.210864",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
@ -99,7 +99,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
@ -120,7 +120,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "current_serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Current Serial No",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
@ -189,7 +189,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-02 13:01:23.580937",
|
||||
"modified": "2023-05-09 18:42:19.224916",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reconciliation Item",
|
||||
|
Loading…
Reference in New Issue
Block a user