mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-13 20:05:09 +00:00
Merge pull request #49328 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -576,6 +576,7 @@ class PaymentReconciliation(Document):
|
||||
"difference_amount": flt(row.get("difference_amount")),
|
||||
"difference_account": row.get("difference_account"),
|
||||
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||
"debit_or_credit_note_posting_date": row.get("debit_or_credit_note_posting_date"),
|
||||
"cost_center": row.get("cost_center"),
|
||||
}
|
||||
)
|
||||
@@ -765,7 +766,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
|
||||
{
|
||||
"doctype": "Journal Entry",
|
||||
"voucher_type": voucher_type,
|
||||
"posting_date": today(),
|
||||
"posting_date": inv.get("debit_or_credit_note_posting_date") or today(),
|
||||
"company": company,
|
||||
"multi_currency": 1 if inv.currency != company_currency else 0,
|
||||
"accounts": [
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"section_break_5",
|
||||
"difference_amount",
|
||||
"gain_loss_posting_date",
|
||||
"debit_or_credit_note_posting_date",
|
||||
"column_break_7",
|
||||
"difference_account",
|
||||
"exchange_rate",
|
||||
@@ -168,12 +169,17 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "debit_or_credit_note_posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Debit / Credit Note Posting Date"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-14 13:38:26.104150",
|
||||
"modified": "2025-08-20 19:12:50.406769",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
@@ -183,4 +189,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class PaymentReconciliationAllocation(Document):
|
||||
amount: DF.Currency
|
||||
cost_center: DF.Link | None
|
||||
currency: DF.Link | None
|
||||
debit_or_credit_note_posting_date: DF.Date | None
|
||||
difference_account: DF.Link | None
|
||||
difference_amount: DF.Currency
|
||||
exchange_rate: DF.Float
|
||||
|
||||
@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_f
|
||||
)
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
|
||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||
from erpnext.accounts.utils import create_payment_ledger_entry, is_immutable_ledger_enabled
|
||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||
|
||||
|
||||
@@ -838,7 +838,3 @@ def validate_allowed_dimensions(gl_entry, dimension_filter_map):
|
||||
),
|
||||
InvalidAccountDimensionError,
|
||||
)
|
||||
|
||||
|
||||
def is_immutable_ledger_enabled():
|
||||
return frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger")
|
||||
|
||||
@@ -1743,40 +1743,38 @@ def create_err_and_its_journals(companies: list | None = None) -> None:
|
||||
jv and frappe.get_doc("Journal Entry", jv).submit()
|
||||
|
||||
|
||||
def _auto_create_exchange_rate_revaluation_for(frequency: str) -> None:
|
||||
"""
|
||||
Internal helper to avoid code duplication and typos.
|
||||
Fetches companies by frequency and triggers ERR.
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": frequency},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_daily() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
_auto_create_exchange_rate_revaluation_for("Daily")
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_weekly() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
_auto_create_exchange_rate_revaluation_for("Weekly")
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_monthly() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Montly"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
_auto_create_exchange_rate_revaluation_for("Monthly")
|
||||
|
||||
|
||||
def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
@@ -1897,6 +1895,9 @@ def create_payment_ledger_entry(
|
||||
|
||||
if cancel:
|
||||
delink_original_entry(ple, partial_cancel=partial_cancel)
|
||||
if is_immutable_ledger_enabled():
|
||||
ple.delinked = 0
|
||||
ple.posting_date = frappe.form_dict.get("posting_date") or getdate()
|
||||
|
||||
ple.flags.ignore_permissions = 1
|
||||
ple.flags.adv_adj = adv_adj
|
||||
@@ -1984,7 +1985,6 @@ def delink_original_entry(pl_entry, partial_cancel=False):
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
query = (
|
||||
qb.update(ple)
|
||||
.set(ple.delinked, True)
|
||||
.set(ple.modified, now())
|
||||
.set(ple.modified_by, frappe.session.user)
|
||||
.where(
|
||||
@@ -2003,6 +2003,9 @@ def delink_original_entry(pl_entry, partial_cancel=False):
|
||||
if partial_cancel:
|
||||
query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no)
|
||||
|
||||
if not is_immutable_ledger_enabled():
|
||||
query = query.set(ple.delinked, True)
|
||||
|
||||
query.run()
|
||||
|
||||
|
||||
@@ -2459,3 +2462,7 @@ def build_qb_match_conditions(doctype, user=None) -> list:
|
||||
criterion.append(cond)
|
||||
|
||||
return criterion
|
||||
|
||||
|
||||
def is_immutable_ledger_enabled():
|
||||
return frappe.get_single_value("Accounts Settings", "enable_immutable_ledger")
|
||||
|
||||
@@ -322,6 +322,9 @@ class Asset(AccountsController):
|
||||
finance_books = get_item_details(self.item_code, self.asset_category, self.gross_purchase_amount)
|
||||
self.set("finance_books", finance_books)
|
||||
|
||||
if self.asset_owner == "Company" and not self.asset_owner_company:
|
||||
self.asset_owner_company = self.company
|
||||
|
||||
def validate_finance_books(self):
|
||||
if not self.calculate_depreciation or len(self.finance_books) == 1:
|
||||
return
|
||||
|
||||
@@ -456,7 +456,7 @@ class AssetDepreciationSchedule(Document):
|
||||
continue
|
||||
depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
|
||||
value_after_depreciation = flt(
|
||||
value_after_depreciation - flt(depreciation_amount),
|
||||
flt(value_after_depreciation) - flt(depreciation_amount),
|
||||
asset_doc.precision("gross_purchase_amount"),
|
||||
)
|
||||
|
||||
|
||||
@@ -599,6 +599,9 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Stopped", "Expired"]],
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "rate", "amount"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
|
||||
@@ -64,6 +64,11 @@ frappe.ui.form.on("Supplier", {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.make_methods = {
|
||||
"Bank Account": () => erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name),
|
||||
"Pricing Rule": () => erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name),
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@@ -235,7 +237,12 @@ def get_list_context(context=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_order(source_name, target_doc=None):
|
||||
def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("get_schedule_dates")
|
||||
@@ -244,6 +251,11 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
|
||||
|
||||
def select_item(d):
|
||||
filtered_items = args.get("filtered_children", [])
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
return child_filter
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Supplier Quotation",
|
||||
source_name,
|
||||
@@ -265,6 +277,7 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
["sales_order", "sales_order"],
|
||||
],
|
||||
"postprocess": update_item,
|
||||
"condition": select_item,
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
|
||||
@@ -108,7 +108,7 @@ status_map = {
|
||||
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
||||
[
|
||||
"Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type in ['Purchase', 'Manufacture']",
|
||||
],
|
||||
[
|
||||
"Transferred",
|
||||
@@ -134,10 +134,6 @@ status_map = {
|
||||
"Partially Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 and self.material_request_type != 'Material Transfer'",
|
||||
],
|
||||
[
|
||||
"Manufactured",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
|
||||
],
|
||||
],
|
||||
"POS Opening Entry": [
|
||||
["Draft", None],
|
||||
|
||||
@@ -462,7 +462,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"max_attachments": 4,
|
||||
"modified": "2025-07-03 10:54:30.444139",
|
||||
"modified": "2025-08-21 17:57:58.314809",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Project",
|
||||
@@ -512,6 +512,7 @@
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "project_name,customer, status, priority, is_active",
|
||||
"show_name_in_global_search": 1,
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
|
||||
@@ -551,6 +551,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
item_code(doc, cdt, cdn) {
|
||||
var me = this;
|
||||
frappe.flags.dialog_set = false;
|
||||
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
var update_stock = 0, show_batch_dialog = 0;
|
||||
|
||||
|
||||
@@ -209,17 +209,9 @@ $.extend(erpnext.utils, {
|
||||
},
|
||||
|
||||
make_bank_account: function (doctype, docname) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.bank_account.bank_account.make_bank_account",
|
||||
args: {
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
},
|
||||
freeze: true,
|
||||
callback: function (r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
},
|
||||
frappe.new_doc("Bank Account", {
|
||||
party_type: doctype,
|
||||
party: docname,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -113,12 +113,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
frappe.flags.trigger_from_barcode_scanner = true;
|
||||
|
||||
const { item_code, barcode, batch_no, serial_no, uom, default_warehouse } = data;
|
||||
|
||||
const warehouse = this.has_last_scanned_warehouse
|
||||
? this.frm.doc.last_scanned_warehouse || default_warehouse
|
||||
: null;
|
||||
|
||||
let row = this.get_row_to_modify_on_scan(item_code, batch_no, uom, barcode, warehouse);
|
||||
let row = this.get_row_to_modify_on_scan(item_code, batch_no, uom, barcode, default_warehouse);
|
||||
const is_new_row = !row?.item_code;
|
||||
if (!row) {
|
||||
if (this.dont_allow_new_row) {
|
||||
@@ -151,7 +146,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
() => this.set_serial_no(row, serial_no),
|
||||
() => this.set_batch_no(row, batch_no),
|
||||
() => this.set_barcode(row, barcode),
|
||||
() => this.set_warehouse(row, warehouse),
|
||||
() => this.set_warehouse(row),
|
||||
() => this.clean_up(),
|
||||
() => this.revert_selector_flag(),
|
||||
() => resolve(row),
|
||||
@@ -412,12 +407,16 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
}
|
||||
}
|
||||
|
||||
async set_warehouse(row, warehouse) {
|
||||
const warehouse_field = this.get_warehouse_field();
|
||||
async set_warehouse(row) {
|
||||
if (!this.has_last_scanned_warehouse) return;
|
||||
|
||||
if (warehouse && frappe.meta.has_field(row.doctype, warehouse_field)) {
|
||||
await frappe.model.set_value(row.doctype, row.name, warehouse_field, warehouse);
|
||||
}
|
||||
const last_scanned_warehouse = this.frm.doc.last_scanned_warehouse;
|
||||
if (!last_scanned_warehouse) return;
|
||||
|
||||
const warehouse_field = this.get_warehouse_field();
|
||||
if (!warehouse_field || !frappe.meta.has_field(row.doctype, warehouse_field)) return;
|
||||
|
||||
await frappe.model.set_value(row.doctype, row.name, warehouse_field, last_scanned_warehouse);
|
||||
}
|
||||
|
||||
show_scan_message(idx, is_existing_row = false, qty = 1) {
|
||||
@@ -438,15 +437,19 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
return is_duplicate;
|
||||
}
|
||||
|
||||
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode, warehouse) {
|
||||
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode, default_warehouse) {
|
||||
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||
|
||||
// Check if batch is scanned and table has batch no field
|
||||
let is_batch_no_scan = batch_no && frappe.meta.has_field(cur_grid.doctype, this.batch_no_field);
|
||||
let check_max_qty = this.max_qty_field && frappe.meta.has_field(cur_grid.doctype, this.max_qty_field);
|
||||
|
||||
const warehouse_field = this.get_warehouse_field();
|
||||
let has_warehouse_field = frappe.meta.has_field(cur_grid.doctype, warehouse_field);
|
||||
const warehouse_field = this.has_last_scanned_warehouse && this.get_warehouse_field();
|
||||
const has_warehouse_field =
|
||||
warehouse_field && frappe.meta.has_field(cur_grid.doctype, warehouse_field);
|
||||
const warehouse = has_warehouse_field
|
||||
? this.frm.doc.last_scanned_warehouse || default_warehouse
|
||||
: null;
|
||||
|
||||
const matching_row = (row) => {
|
||||
const item_match = row.item_code == item_code;
|
||||
@@ -509,7 +512,8 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
handle_warehouse_scan(data) {
|
||||
const warehouse = data.warehouse;
|
||||
const warehouse_field = this.get_warehouse_field();
|
||||
const warehouse_field_label = frappe.meta.get_label(this.items_table_name, warehouse_field);
|
||||
const cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||
const warehouse_field_label = frappe.meta.get_label(cur_grid.doctype, warehouse_field);
|
||||
|
||||
if (!this.last_scanned_warehouse_initialized) {
|
||||
this.setup_last_scanned_warehouse();
|
||||
|
||||
@@ -14,6 +14,7 @@ frappe.ui.form.on("Customer", {
|
||||
method: "erpnext.selling.doctype.customer.customer.make_opportunity",
|
||||
frm: cur_frm,
|
||||
}),
|
||||
"Bank Account": () => erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name),
|
||||
};
|
||||
|
||||
frm.add_fetch("lead_name", "company_name", "customer_name");
|
||||
|
||||
@@ -1144,6 +1144,17 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False, a
|
||||
target.debit_to = get_party_account("Customer", source.customer, source.company)
|
||||
|
||||
def update_item(source, target, source_parent):
|
||||
def get_billed_qty(so_item_name):
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
table = frappe.qb.DocType("Sales Invoice Item")
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(Sum(table.qty).as_("qty"))
|
||||
.where((table.docstatus == 1) & (table.so_detail == so_item_name))
|
||||
)
|
||||
return query.run(pluck="qty")[0] or 0
|
||||
|
||||
if source_parent.has_unit_price_items:
|
||||
# 0 Amount rows (as seen in Unit Price Items) should be mapped as it is
|
||||
pending_amount = flt(source.amount) - flt(source.billed_amt)
|
||||
@@ -1153,8 +1164,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False, a
|
||||
|
||||
target.base_amount = target.amount * flt(source_parent.conversion_rate)
|
||||
target.qty = (
|
||||
target.amount / flt(source.rate)
|
||||
if (source.rate and source.billed_amt)
|
||||
source.qty - get_billed_qty(source.name)
|
||||
if (source.qty and source.billed_amt)
|
||||
else (source.qty if is_unit_price_row(source) else source.qty - source.returned_qty)
|
||||
)
|
||||
|
||||
|
||||
@@ -1830,7 +1830,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
wo.reload()
|
||||
so.reload()
|
||||
self.assertEqual(so.items[0].work_order_qty, wo.produced_qty)
|
||||
self.assertEqual(mr.status, "Manufactured")
|
||||
self.assertEqual(mr.status, "Ordered")
|
||||
|
||||
@change_settings(
|
||||
"Accounts Settings",
|
||||
@@ -2396,6 +2396,30 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
self.assertFalse(so.per_billed)
|
||||
self.assertEqual(so.status, "To Deliver and Bill")
|
||||
|
||||
def test_pending_quantity_after_update_item_during_invoice_creation(self):
|
||||
so = make_sales_order(qty=30, rate=100)
|
||||
|
||||
si1 = make_sales_invoice(so.name)
|
||||
si1.get("items")[0].qty = 10
|
||||
si1.insert()
|
||||
si1.submit()
|
||||
|
||||
first_item_of_so = so.get("items")[0]
|
||||
trans_item = json.dumps(
|
||||
[
|
||||
{
|
||||
"item_code": first_item_of_so.item_code,
|
||||
"rate": 1000,
|
||||
"qty": first_item_of_so.qty,
|
||||
"docname": first_item_of_so.name,
|
||||
},
|
||||
]
|
||||
)
|
||||
update_child_qty_rate("Sales Order", trans_item, so.name)
|
||||
|
||||
si2 = make_sales_invoice(so.name)
|
||||
self.assertEqual(si2.items[0].qty, 20)
|
||||
|
||||
|
||||
def automatically_fetch_payment_terms(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
|
||||
@@ -21,6 +21,12 @@ erpnext.setup.EmployeeController = class EmployeeController extends frappe.ui.fo
|
||||
};
|
||||
|
||||
frappe.ui.form.on("Employee", {
|
||||
setup: function (frm) {
|
||||
frm.make_methods = {
|
||||
"Bank Account": () => erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name),
|
||||
};
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
frm.set_query("department", function () {
|
||||
return {
|
||||
|
||||
@@ -35,7 +35,7 @@ frappe.listview_settings["Material Request"] = {
|
||||
return [__("Partially Received"), "yellow", "per_received,<,100"];
|
||||
} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) == 100) {
|
||||
return [__("Received"), "green", "per_received,=,100"];
|
||||
} else if (doc.material_request_type == "Purchase") {
|
||||
} else if (["Purchase", "Manufacture"].includes(doc.material_request_type)) {
|
||||
return [__("Ordered"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Material Transfer") {
|
||||
return [__("Transfered"), "green", "per_ordered,=,100"];
|
||||
@@ -43,8 +43,6 @@ frappe.listview_settings["Material Request"] = {
|
||||
return [__("Issued"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Customer Provided") {
|
||||
return [__("Received"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Manufacture") {
|
||||
return [__("Manufactured"), "green", "per_ordered,=,100"];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -866,6 +866,23 @@ class TestMaterialRequest(FrappeTestCase):
|
||||
for perm in permissions:
|
||||
perm.delete()
|
||||
|
||||
def test_manufacture_type_status_over_wo(self):
|
||||
from erpnext.stock.doctype.material_request.material_request import raise_work_orders
|
||||
|
||||
mr = make_material_request(
|
||||
item_code="_Test FG Item", material_request_type="Manufacture", do_not_submit=False
|
||||
)
|
||||
|
||||
work_order = raise_work_orders(mr.name)
|
||||
wo = frappe.get_doc("Work Order", work_order[0])
|
||||
wo.wip_warehouse = "_Test Warehouse 1 - _TC"
|
||||
wo.submit()
|
||||
|
||||
mr.reload()
|
||||
|
||||
self.assertEqual(mr.per_ordered, 100)
|
||||
self.assertEqual(mr.status, "Ordered")
|
||||
|
||||
|
||||
def get_in_transit_warehouse(company):
|
||||
if not frappe.db.exists("Warehouse Type", "Transit"):
|
||||
|
||||
@@ -1322,8 +1322,8 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
|
||||
"postprocess": update_item,
|
||||
"filter": lambda d: (
|
||||
get_pending_qty(d)[0] <= 0 if not doc.get("is_return") else get_pending_qty(d)[0] > 0
|
||||
)
|
||||
and select_item(d),
|
||||
),
|
||||
"condition": select_item,
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
|
||||
@@ -608,7 +608,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
||||
def test_serial_no_valuation_for_legacy_ledgers(self):
|
||||
sn_item = make_item(
|
||||
"Test Serial No Valuation for Legacy Ledgers",
|
||||
properties={"has_serial_no": 1, "serial_no_series": "SNN-TSNVL.-#####"},
|
||||
properties={"has_serial_no": 1, "serial_no_series": "SNN-TSNVL-.#####"},
|
||||
).name
|
||||
|
||||
serial_nos = []
|
||||
|
||||
@@ -1277,17 +1277,33 @@ def get_pos_profile(company, pos_profile=None, user=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_conversion_factor(item_code, uom):
|
||||
variant_of = frappe.db.get_value("Item", item_code, "variant_of", cache=True)
|
||||
filters = {"parent": item_code, "uom": uom}
|
||||
item = frappe.get_cached_value("Item", item_code, ["variant_of", "stock_uom"], as_dict=True)
|
||||
if not item_code or not item or uom == item.stock_uom:
|
||||
return {"conversion_factor": 1.0}
|
||||
|
||||
item_codes = [item_code]
|
||||
if item.variant_of:
|
||||
item_codes.append(item.variant_of)
|
||||
|
||||
parent = frappe.qb.DocType("Item")
|
||||
child = frappe.qb.DocType("UOM Conversion Detail")
|
||||
query = (
|
||||
frappe.qb.from_(parent)
|
||||
.join(child)
|
||||
.on(parent.name == child.parent)
|
||||
.select(child.conversion_factor)
|
||||
.where((parent.name.isin(item_codes)) & (child.uom == uom))
|
||||
.orderby(parent.has_variants)
|
||||
.limit(1)
|
||||
)
|
||||
conversion_factor = query.run(pluck="conversion_factor")
|
||||
|
||||
if variant_of:
|
||||
filters["parent"] = ("in", (item_code, variant_of))
|
||||
conversion_factor = frappe.get_all("UOM Conversion Detail", filters, pluck="conversion_factor")
|
||||
if not conversion_factor:
|
||||
stock_uom = frappe.db.get_value("Item", item_code, "stock_uom")
|
||||
conversion_factor = [get_uom_conv_factor(uom, stock_uom) or 1]
|
||||
conversion_factor = get_uom_conv_factor(uom, item.stock_uom)
|
||||
else:
|
||||
conversion_factor = conversion_factor[0]
|
||||
|
||||
return {"conversion_factor": conversion_factor[-1]}
|
||||
return {"conversion_factor": conversion_factor or 1.0}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -61,10 +61,10 @@ frappe.query_reports["Stock Balance"] = {
|
||||
},
|
||||
});
|
||||
|
||||
data = data.map(({ name, description }) => {
|
||||
data = data.map(({ name, ...rest }) => {
|
||||
return {
|
||||
value: name,
|
||||
description: description,
|
||||
description: Object.values(rest),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -56,11 +56,10 @@ frappe.query_reports["Stock Ledger"] = {
|
||||
as_dict: 1,
|
||||
},
|
||||
});
|
||||
|
||||
data = data.map(({ name, description }) => {
|
||||
data = data.map(({ name, ...rest }) => {
|
||||
return {
|
||||
value: name,
|
||||
description: description,
|
||||
description: Object.values(rest),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user