Merge pull request #33163 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
Deepesh Garg
2022-11-29 18:48:14 +05:30
committed by GitHub
64 changed files with 3470 additions and 373 deletions

View File

@@ -91,7 +91,7 @@
},
{
"default": "0",
"description": "Enabling ensure each Sales Invoice has a unique value in Supplier Invoice No. field",
"description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
"fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness"
@@ -354,7 +354,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-07-11 13:37:50.605141",
"modified": "2022-11-27 21:49:52.538655",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -37,6 +37,14 @@ frappe.ui.form.on("Bank Clearance", {
refresh: function(frm) {
frm.disable_save();
if (frm.doc.account && frm.doc.from_date && frm.doc.to_date) {
frm.add_custom_button(__('Get Payment Entries'), () =>
frm.trigger("get_payment_entries")
);
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
}
},
update_clearance_date: function(frm) {
@@ -46,22 +54,30 @@ frappe.ui.form.on("Bank Clearance", {
callback: function(r, rt) {
frm.refresh_field("payment_entries");
frm.refresh_fields();
if (!frm.doc.payment_entries.length) {
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
frm.change_custom_button_type('Update Clearance Date', null, 'default');
}
}
});
},
get_payment_entries: function(frm) {
return frappe.call({
method: "get_payment_entries",
doc: frm.doc,
callback: function(r, rt) {
frm.refresh_field("payment_entries");
frm.refresh_fields();
$(frm.fields_dict.payment_entries.wrapper).find("[data-fieldname=amount]").each(function(i,v){
if (i !=0){
$(v).addClass("text-right")
}
})
if (frm.doc.payment_entries.length) {
frm.add_custom_button(__('Update Clearance Date'), () =>
frm.trigger("update_clearance_date")
);
frm.change_custom_button_type('Get Payment Entries', null, 'default');
frm.change_custom_button_type('Update Clearance Date', null, 'primary');
}
}
});
}

View File

@@ -1,4 +1,5 @@
{
"actions": [],
"allow_copy": 1,
"creation": "2013-01-10 16:34:05",
"doctype": "DocType",
@@ -13,11 +14,8 @@
"bank_account",
"include_reconciled_entries",
"include_pos_transactions",
"get_payment_entries",
"section_break_10",
"payment_entries",
"update_clearance_date",
"total_amount"
"payment_entries"
],
"fields": [
{
@@ -76,11 +74,6 @@
"fieldtype": "Check",
"label": "Include POS Transactions"
},
{
"fieldname": "get_payment_entries",
"fieldtype": "Button",
"label": "Get Payment Entries"
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break"
@@ -91,25 +84,14 @@
"fieldtype": "Table",
"label": "Payment Entries",
"options": "Bank Clearance Detail"
},
{
"fieldname": "update_clearance_date",
"fieldtype": "Button",
"label": "Update Clearance Date"
},
{
"fieldname": "total_amount",
"fieldtype": "Currency",
"label": "Total Amount",
"options": "account_currency",
"read_only": 1
}
],
"hide_toolbar": 1,
"icon": "fa fa-check",
"idx": 1,
"issingle": 1,
"modified": "2020-04-06 16:12:06.628008",
"links": [],
"modified": "2022-11-28 17:24:13.008692",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Clearance",
@@ -126,5 +108,6 @@
"quick_entry": 1,
"read_only": 1,
"sort_field": "modified",
"sort_order": "ASC"
"sort_order": "ASC",
"states": []
}

View File

@@ -179,7 +179,6 @@ class BankClearance(Document):
)
self.set("payment_entries", [])
self.total_amount = 0.0
default_currency = erpnext.get_default_currency()
for d in entries:
@@ -198,7 +197,6 @@ class BankClearance(Document):
d.pop("debit")
d.pop("account_currency")
row.update(d)
self.total_amount += flt(amount)
@frappe.whitelist()
def update_clearance_date(self):

View File

@@ -5,6 +5,8 @@
frappe.provide("erpnext.accounts");
erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController {
settings = {};
setup(doc) {
this.setup_posting_date_time_check();
super.setup(doc);
@@ -12,21 +14,37 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
company() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
this.frm.set_value("set_warehouse", "");
this.frm.set_value("taxes_and_charges", "");
}
onload(doc) {
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
this.frm.refresh_fields();
}
this.frm.set_query("set_warehouse", function(doc) {
return {
filters: {
company: doc.company ? doc.company : '',
}
}
});
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
onload_post_render(frm) {
this.pos_profile(frm);
}
refresh(doc) {
super.refresh();
if (doc.docstatus == 1 && !doc.is_return) {
this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
@@ -36,6 +54,18 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
this.frm.return_print_format = "Sales Invoice Return";
this.frm.set_value('consolidated_invoice', '');
}
this.frm.set_query("customer", (function () {
const customer_groups = this.settings?.customer_groups;
if (!customer_groups?.length) return {};
return {
filters: {
customer_group: ["in", customer_groups],
}
}
}).bind(this));
}
is_pos() {
@@ -88,6 +118,25 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
});
}
pos_profile(frm) {
if (!frm.pos_profile || frm.pos_profile == '') {
this.update_customer_groups_settings([]);
return;
}
frappe.call({
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data",
args: { "pos_profile": frm.pos_profile },
callback: ({ message: profile }) => {
this.update_customer_groups_settings(profile?.customer_groups);
},
});
}
update_customer_groups_settings(customer_groups) {
this.settings.customer_groups = customer_groups?.map((group) => group.name)
}
amount(){
this.write_off_outstanding_amount_automatically()
}

View File

@@ -69,6 +69,7 @@
"tax_category",
"column_break_49",
"shipping_rule",
"incoterm",
"section_break_51",
"taxes",
"totals",
@@ -1534,13 +1535,19 @@
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
"print_hide": 1
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2022-11-22 12:44:29.935567",
"modified": "2022-11-27 16:28:45.559785",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -606,7 +606,7 @@ class PurchaseInvoice(BuyingController):
def make_supplier_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
# because rounded_total had value even before introduction of posting GLE based on rounded total
grand_total = (
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
)
@@ -809,10 +809,7 @@ class PurchaseInvoice(BuyingController):
else item.deferred_expense_account
)
if not item.is_fixed_asset:
dummy, amount = self.get_amount_and_base_amount(item, None)
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
dummy, amount = self.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:

View File

@@ -64,6 +64,7 @@
"taxes_and_charges",
"column_break_38",
"shipping_rule",
"incoterm",
"column_break_55",
"tax_category",
"section_break_40",
@@ -2097,6 +2098,12 @@
"hide_seconds": 1,
"label": "Write Off",
"width": "50%"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-file-text",
@@ -2109,7 +2116,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-11-15 09:33:47.870616",
"modified": "2022-11-17 17:17:10.883487",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -226,6 +226,42 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices):
d.cancel()
orders = []
po = create_purchase_order(supplier="Test TDS Supplier4", rate=20000, do_not_save=True)
po.extend(
"items",
[
{
"doctype": "Purchase Order 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 Order 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,
},
],
)
po.save()
po.submit()
orders.append(po)
self.assertEqual(po.taxes[0].tax_amount, 5500)
# cancel orders to avoid clashing
for d in reversed(orders):
d.cancel()
def test_multi_category_single_supplier(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"
@@ -348,6 +384,39 @@ def create_purchase_invoice(**args):
return pi
def create_purchase_order(**args):
# return purchase order doc object
item = frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name")
args = frappe._dict(args)
po = frappe.get_doc(
{
"doctype": "Purchase Order",
"transaction_date": today(),
"schedule_date": today(),
"apply_tds": 0 if args.do_not_apply_tds else 1,
"supplier": args.supplier,
"company": "_Test Company",
"taxes_and_charges": "",
"currency": "INR",
"taxes": [],
"items": [
{
"doctype": "Purchase Order Item",
"item_code": item,
"qty": args.qty or 1,
"rate": args.rate or 10000,
"cost_center": "Main - _TC",
"expense_account": "Stock Received But Not Billed - _TC",
}
],
}
)
po.save()
return po
def create_sales_invoice(**args):
# return sales invoice doc object
item = frappe.db.get_value("Item", {"item_name": "TCS Item"}, "name")

View File

@@ -394,20 +394,22 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
)
round_off_account_exists = False
round_off_gle = frappe._dict()
for d in gl_map:
if d.account == round_off_account:
round_off_gle = d
if d.debit:
debit_credit_diff -= flt(d.debit)
else:
debit_credit_diff += flt(d.credit)
round_off_account_exists = True
round_off_account_exists = False
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
gl_map.remove(round_off_gle)
return
if gl_map[0].voucher_type != "Period Closing Voucher":
for d in gl_map:
if d.account == round_off_account:
round_off_gle = d
if d.debit:
debit_credit_diff -= flt(d.debit) - flt(d.credit)
else:
debit_credit_diff += flt(d.credit)
round_off_account_exists = True
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
gl_map.remove(round_off_gle)
return
if not round_off_gle:
for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
@@ -430,7 +432,6 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
)
update_accounting_dimensions(round_off_gle)
if not round_off_account_exists:
gl_map.append(round_off_gle)

View File

@@ -224,7 +224,10 @@ class TestAsset(AssetSetup):
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
self.assertEquals(
accumulated_depr_amount,
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
)
self.assertEqual(asset.status, "Scrapped")
self.assertTrue(asset.journal_entry_for_scrap)

View File

@@ -54,6 +54,8 @@
"column_break_26",
"total",
"net_total",
"tax_withholding_net_total",
"base_tax_withholding_net_total",
"section_break_48",
"pricing_rules",
"raw_material_details",
@@ -65,6 +67,7 @@
"tax_category",
"column_break_50",
"shipping_rule",
"incoterm",
"section_break_52",
"taxes",
"totals",
@@ -1220,6 +1223,26 @@
"label": "Additional Info",
"oldfieldtype": "Section Break"
},
{
"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
},
{
"fieldname": "column_break_99",
"fieldtype": "Column Break"
@@ -1227,13 +1250,19 @@
{
"fieldname": "column_break_103",
"fieldtype": "Column Break"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2022-11-17 12:34:36.033363",
"modified": "2022-11-17 17:28:07.729943",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -44,6 +44,7 @@
"discount_amount",
"base_rate_with_margin",
"sec_break2",
"apply_tds",
"rate",
"amount",
"item_tax_template",
@@ -889,6 +890,12 @@
{
"fieldname": "column_break_54",
"fieldtype": "Column Break"
},
{
"default": "1",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply TDS"
}
],
"idx": 1,

View File

@@ -28,6 +28,7 @@
"sec_break_email_2",
"message_for_supplier",
"terms_section_break",
"incoterm",
"tc_name",
"terms",
"printing_settings",
@@ -271,13 +272,19 @@
"fieldname": "schedule_date",
"fieldtype": "Date",
"label": "Required Date"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-shopping-cart",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-04-06 17:47:49.909000",
"modified": "2022-11-17 17:26:33.770993",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
@@ -345,5 +352,6 @@
"search_fields": "status, transaction_date",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC"
}
"sort_order": "DESC",
"states": []
}

View File

@@ -45,6 +45,7 @@
"tax_category",
"column_break_36",
"shipping_rule",
"incoterm",
"section_break_38",
"taxes",
"totals",
@@ -823,6 +824,12 @@
{
"fieldname": "column_break_85",
"fieldtype": "Column Break"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-shopping-cart",
@@ -830,7 +837,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-09-27 18:20:09.462037",
"modified": "2022-11-17 17:27:32.179686",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@@ -239,6 +239,14 @@ class AccountsController(TransactionBase):
else:
item.set(field_map.get(self.doctype), default_deferred_account)
def validate_auto_repeat_subscription_dates(self):
if (
self.get("from_date")
and self.get("to_date")
and getdate(self.from_date) > getdate(self.to_date)
):
frappe.throw(_("To Date cannot be before From Date"), title=_("Invalid Auto Repeat Date"))
def validate_deferred_start_and_end_date(self):
for d in self.items:
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):

View File

@@ -41,6 +41,7 @@ class BuyingController(SubcontractingController):
self.validate_from_warehouse()
self.set_supplier_address()
self.validate_asset_return()
self.validate_auto_repeat_subscription_dates()
if self.doctype == "Purchase Invoice":
self.validate_purchase_receipt_if_update_stock()

View File

@@ -40,6 +40,7 @@ class SellingController(StockController):
self.set_customer_address()
self.validate_for_duplicate_items()
self.validate_target_warehouse()
self.validate_auto_repeat_subscription_dates()
def set_missing_values(self, for_validate=False):
@@ -581,6 +582,7 @@ class SellingController(StockController):
"customer_address": "address_display",
"shipping_address_name": "shipping_address",
"company_address": "company_address_display",
"dispatch_address_name": "dispatch_address",
}
for address_field, address_display_field in address_dict.items():

View File

@@ -120,7 +120,7 @@ def link_open_tasks(ref_doctype, ref_docname, doc):
todo_doc = frappe.get_doc("ToDo", todo.name)
todo_doc.reference_type = doc.doctype
todo_doc.reference_name = doc.name
todo_doc.db_update()
todo_doc.save()
def link_open_events(ref_doctype, ref_docname, doc):

View File

@@ -191,7 +191,9 @@ def get_total_pledged_security_value(loan):
for security, qty in pledged_securities.items():
after_haircut_percentage = 100 - hair_cut_map.get(security)
security_value += (loan_security_price_map.get(security) * qty * after_haircut_percentage) / 100
security_value += (
loan_security_price_map.get(security, 0) * qty * after_haircut_percentage
) / 100
return security_value

View File

@@ -33,6 +33,11 @@ frappe.ui.form.on('Job Card', {
return;
}
let has_stock_entry = frm.doc.__onload &&
frm.doc.__onload.has_stock_entry ? true : false;
frm.toggle_enable("for_quantity", !has_stock_entry);
if (!frm.is_new() && has_items && frm.doc.docstatus < 2) {
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;

View File

@@ -57,6 +57,10 @@ class JobCard(Document):
)
self.set_onload("job_card_excess_transfer", excess_transfer)
self.set_onload("work_order_closed", self.is_work_order_closed())
self.set_onload("has_stock_entry", self.has_stock_entry())
def has_stock_entry(self):
return frappe.db.exists("Stock Entry", {"job_card": self.name, "docstatus": ["!=", 2]})
def before_validate(self):
self.set_wip_warehouse()

View File

@@ -47,7 +47,7 @@
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Warehouse",
"label": "For Warehouse",
"options": "Warehouse",
"reqd": 1
},
@@ -173,7 +173,7 @@
],
"istable": 1,
"links": [],
"modified": "2021-08-23 18:17:58.400462",
"modified": "2022-11-26 14:59:25.879631",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Plan Item",
@@ -182,5 +182,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -3,13 +3,13 @@
frappe.ui.form.on('Production Plan', {
before_save: function(frm) {
before_save(frm) {
// preserve temporary names on production plan item to re-link sub-assembly items
frm.doc.po_items.forEach(item => {
item.temporary_name = item.name;
});
},
setup: function(frm) {
setup(frm) {
frm.custom_make_buttons = {
'Work Order': 'Work Order / Subcontract PO',
'Material Request': 'Material Request',
@@ -70,7 +70,7 @@ frappe.ui.form.on('Production Plan', {
}
},
refresh: function(frm) {
refresh(frm) {
if (frm.doc.docstatus === 1) {
frm.trigger("show_progress");
@@ -158,7 +158,7 @@ frappe.ui.form.on('Production Plan', {
set_field_options("projected_qty_formula", projected_qty_formula);
},
close_open_production_plan: (frm, close=false) => {
close_open_production_plan(frm, close=false) {
frappe.call({
method: "set_status",
freeze: true,
@@ -170,7 +170,7 @@ frappe.ui.form.on('Production Plan', {
});
},
make_work_order: function(frm) {
make_work_order(frm) {
frappe.call({
method: "make_work_order",
freeze: true,
@@ -181,7 +181,7 @@ frappe.ui.form.on('Production Plan', {
});
},
make_material_request: function(frm) {
make_material_request(frm) {
frappe.confirm(__("Do you want to submit the material request"),
function() {
@@ -193,7 +193,7 @@ frappe.ui.form.on('Production Plan', {
);
},
create_material_request: function(frm, submit) {
create_material_request(frm, submit) {
frm.doc.submit_material_request = submit;
frappe.call({
@@ -206,7 +206,7 @@ frappe.ui.form.on('Production Plan', {
});
},
get_sales_orders: function(frm) {
get_sales_orders(frm) {
frappe.call({
method: "get_open_sales_orders",
doc: frm.doc,
@@ -216,7 +216,7 @@ frappe.ui.form.on('Production Plan', {
});
},
get_material_request: function(frm) {
get_material_request(frm) {
frappe.call({
method: "get_pending_material_requests",
doc: frm.doc,
@@ -226,7 +226,7 @@ frappe.ui.form.on('Production Plan', {
});
},
get_items: function (frm) {
get_items(frm) {
frm.clear_table('prod_plan_references');
frappe.call({
@@ -238,7 +238,7 @@ frappe.ui.form.on('Production Plan', {
}
});
},
combine_items: function (frm) {
combine_items(frm) {
frm.clear_table("prod_plan_references");
frappe.call({
@@ -254,14 +254,14 @@ frappe.ui.form.on('Production Plan', {
});
},
combine_sub_items: (frm) => {
combine_sub_items(frm) {
if (frm.doc.sub_assembly_items.length > 0) {
frm.clear_table("sub_assembly_items");
frm.trigger("get_sub_assembly_items");
}
},
get_sub_assembly_items: function(frm) {
get_sub_assembly_items(frm) {
frm.dirty();
frappe.call({
@@ -274,9 +274,25 @@ frappe.ui.form.on('Production Plan', {
});
},
get_items_for_mr: function(frm) {
toggle_for_warehouse(frm) {
frm.toggle_reqd("for_warehouse", true);
},
get_items_for_mr(frm) {
if (!frm.doc.for_warehouse) {
frappe.throw(__("To make material requests, 'Make Material Request for Warehouse' field is mandatory"));
frm.trigger("toggle_for_warehouse");
frappe.throw(__("Select the Warehouse"));
}
frm.events.get_items_for_material_requests(frm, [{
warehouse: frm.doc.for_warehouse
}]);
},
transfer_materials(frm) {
if (!frm.doc.for_warehouse) {
frm.trigger("toggle_for_warehouse");
frappe.throw(__("Select the Warehouse"));
}
if (frm.doc.ignore_existing_ordered_qty) {
@@ -287,18 +303,10 @@ frappe.ui.form.on('Production Plan', {
title: title,
fields: [
{
'label': __('Target Warehouse'),
'fieldtype': 'Link',
'fieldname': 'target_warehouse',
'read_only': true,
'default': frm.doc.for_warehouse
},
{
'label': __('Source Warehouses (Optional)'),
'label': __('Transfer From Warehouses'),
'fieldtype': 'Table MultiSelect',
'fieldname': 'warehouses',
'options': 'Production Plan Material Request Warehouse',
'description': __('If source warehouse selected then system will create the material request with type Material Transfer from Source to Target warehouse. If not selected then will create the material request with type Purchase for the target warehouse.'),
get_query: function () {
return {
filters: {
@@ -307,6 +315,13 @@ frappe.ui.form.on('Production Plan', {
};
},
},
{
'label': __('For Warehouse'),
'fieldtype': 'Link',
'fieldname': 'target_warehouse',
'read_only': true,
'default': frm.doc.for_warehouse
}
]
});
@@ -320,8 +335,8 @@ frappe.ui.form.on('Production Plan', {
}
},
get_items_for_material_requests: function(frm, warehouses) {
const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
get_items_for_material_requests(frm, warehouses) {
let set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty',
'reserved_qty_for_production', 'material_request_type'];
@@ -335,13 +350,13 @@ frappe.ui.form.on('Production Plan', {
callback: function(r) {
if(r.message) {
frm.set_value('mr_items', []);
$.each(r.message, function(i, d) {
var item = frm.add_child('mr_items');
for (let key in d) {
if (d[key] && in_list(set_fields, key)) {
item[key] = d[key];
r.message.forEach(row => {
let d = frm.add_child('mr_items');
set_fields.forEach(field => {
if (row[field]) {
d[field] = row[field];
}
}
});
});
}
refresh_field('mr_items');
@@ -349,13 +364,7 @@ frappe.ui.form.on('Production Plan', {
});
},
for_warehouse: function(frm) {
if (frm.doc.mr_items && frm.doc.for_warehouse) {
frm.trigger("get_items_for_mr");
}
},
download_materials_required: function(frm) {
download_materials_required(frm) {
const fields = [{
fieldname: 'warehouses',
fieldtype: 'Table MultiSelect',
@@ -381,7 +390,7 @@ frappe.ui.form.on('Production Plan', {
}, __('Select Warehouses to get Stock for Materials Planning'), __('Get Stock'));
},
show_progress: function(frm) {
show_progress(frm) {
var bars = [];
var message = '';
var title = '';
@@ -416,7 +425,7 @@ frappe.ui.form.on('Production Plan', {
});
frappe.ui.form.on("Production Plan Item", {
item_code: function(frm, cdt, cdn) {
item_code(frm, cdt, cdn) {
const row = locals[cdt][cdn];
if (row.item_code) {
frappe.call({
@@ -435,7 +444,7 @@ frappe.ui.form.on("Production Plan Item", {
});
frappe.ui.form.on("Material Request Plan Item", {
warehouse: function(frm, cdt, cdn) {
warehouse(frm, cdt, cdn) {
const row = locals[cdt][cdn];
if (row.warehouse && row.item_code && frm.doc.company) {
frappe.call({

View File

@@ -38,6 +38,8 @@
"get_sub_assembly_items",
"combine_sub_items",
"sub_assembly_items",
"download_materials_request_plan_section_section",
"download_materials_required",
"material_request_planning",
"include_non_stock_items",
"include_subcontracted_items",
@@ -45,8 +47,8 @@
"ignore_existing_ordered_qty",
"column_break_25",
"for_warehouse",
"download_materials_required",
"get_items_for_mr",
"transfer_materials",
"section_break_27",
"mr_items",
"other_details",
@@ -206,7 +208,7 @@
{
"fieldname": "material_request_planning",
"fieldtype": "Section Break",
"label": "Material Requirement Planning"
"label": "Material Request Planning"
},
{
"default": "1",
@@ -235,12 +237,12 @@
"depends_on": "eval:!doc.__islocal",
"fieldname": "download_materials_required",
"fieldtype": "Button",
"label": "Download Required Materials"
"label": "Download Materials Request Plan"
},
{
"fieldname": "get_items_for_mr",
"fieldtype": "Button",
"label": "Get Raw Materials For Production"
"label": "Get Raw Materials for Purchase"
},
{
"fieldname": "section_break_27",
@@ -304,7 +306,7 @@
{
"fieldname": "for_warehouse",
"fieldtype": "Link",
"label": "Make Material Request for Warehouse",
"label": "Raw Materials Warehouse",
"options": "Warehouse"
},
{
@@ -378,13 +380,24 @@
"fieldname": "combine_sub_items",
"fieldtype": "Check",
"label": "Consolidate Sub Assembly Items"
},
{
"fieldname": "transfer_materials",
"fieldtype": "Button",
"label": "Get Raw Materials for Transfer"
},
{
"collapsible": 1,
"fieldname": "download_materials_request_plan_section_section",
"fieldtype": "Section Break",
"label": "Download Materials Request Plan Section"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-03-25 09:15:25.017664",
"modified": "2022-11-26 14:51:08.774372",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",

View File

@@ -312,6 +312,9 @@ class ProductionPlan(Document):
def add_items(self, items):
refs = {}
for data in items:
if not data.pending_qty:
continue
item_details = get_item_details(data.item_code)
if self.combine_items:
if item_details.bom_no in refs:
@@ -518,6 +521,9 @@ class ProductionPlan(Document):
subcontracted_po.setdefault(row.supplier, []).append(row)
continue
if row.type_of_manufacturing == "Material Request":
continue
work_order_data = {
"wip_warehouse": default_warehouses.get("wip_warehouse"),
"fg_warehouse": default_warehouses.get("fg_warehouse"),
@@ -1158,6 +1164,7 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
subquery = frappe.qb.from_(wh).select(wh.name).where(wh.company == company)
warehouse = ""
if not all_warehouse:
warehouse = for_warehouse or row.get("source_warehouse") or row.get("default_warehouse")
@@ -1223,6 +1230,21 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
doc["mr_items"] = []
po_items = doc.get("po_items") if doc.get("po_items") else doc.get("items")
if doc.get("sub_assembly_items"):
for sa_row in doc.sub_assembly_items:
sa_row = frappe._dict(sa_row)
if sa_row.type_of_manufacturing == "Material Request":
po_items.append(
frappe._dict(
{
"item_code": sa_row.production_item,
"required_qty": sa_row.qty,
"include_exploded_items": 0,
}
)
)
# Check for empty table or empty rows
if not po_items or not [row.get("item_code") for row in po_items if row.get("item_code")]:
frappe.throw(

View File

@@ -840,6 +840,34 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(row.uom, "Nos")
self.assertEqual(row.qty, 1)
def test_material_request_for_sub_assembly_items(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
bom_tree = {
"Fininshed Goods1 For MR": {
"SubAssembly1 For MR": {"SubAssembly1-1 For MR": {"ChildPart1 For MR": {}}}
}
}
parent_bom = create_nested_bom(bom_tree, prefix="")
plan = create_production_plan(
item_code=parent_bom.item, planned_qty=10, ignore_existing_ordered_qty=1, do_not_submit=1
)
plan.get_sub_assembly_items()
mr_items = []
for row in plan.sub_assembly_items:
mr_items.append(row.production_item)
row.type_of_manufacturing = "Material Request"
plan.save()
items = get_items_for_material_requests(plan.as_dict())
validate_mr_items = [d.get("item_code") for d in items]
for item_code in mr_items:
self.assertTrue(item_code in validate_mr_items)
def create_production_plan(**args):
"""

View File

@@ -83,7 +83,7 @@
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
"label": "For Warehouse",
"label": "FG Warehouse",
"options": "Warehouse"
},
{
@@ -216,7 +216,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-03-24 04:54:09.940224",
"modified": "2022-11-25 14:15:40.061514",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Item",

View File

@@ -169,7 +169,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Manufacturing Type",
"options": "In House\nSubcontract"
"options": "In House\nSubcontract\nMaterial Request"
},
{
"fieldname": "supplier",
@@ -188,7 +188,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-01-30 21:31:10.527559",
"modified": "2022-11-28 13:50:15.116082",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",

View File

@@ -54,11 +54,11 @@ frappe.query_reports["Job Card Summary"] = {
options: ["", "Open", "Work In Progress", "Completed", "On Hold"]
},
{
label: __("Sales Orders"),
fieldname: "sales_order",
label: __("Work Orders"),
fieldname: "work_order",
fieldtype: "MultiSelectList",
get_data: function(txt) {
return frappe.db.get_link_options('Sales Order', txt);
return frappe.db.get_link_options('Work Order', txt);
}
},
{

View File

@@ -36,10 +36,14 @@ def get_data(filters):
"total_time_in_mins",
]
for field in ["work_order", "workstation", "operation", "status", "company"]:
for field in ["work_order", "production_item"]:
if filters.get(field):
query_filters[field] = ("in", filters.get(field))
for field in ["workstation", "operation", "status", "company"]:
if filters.get(field):
query_filters[field] = filters.get(field)
data = frappe.get_all("Job Card", fields=fields, filters=query_filters)
if not data:

View File

@@ -39,10 +39,14 @@ def get_data(filters):
"lead_time",
]
for field in ["sales_order", "production_item", "status", "company"]:
for field in ["sales_order", "production_item"]:
if filters.get(field):
query_filters[field] = ("in", filters.get(field))
for field in ["status", "company"]:
if filters.get(field):
query_filters[field] = filters.get(field)
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))

View File

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

View File

@@ -0,0 +1,31 @@
import frappe
from erpnext.setup.doctype.incoterm.incoterm import create_incoterms
def execute():
create_incoterms()
migrate_shipments()
def migrate_shipments():
if not frappe.db.count("Shipment"):
return
OLD_VALUES = [
"EXW (Ex Works)",
"FCA (Free Carrier)",
"FOB (Free On Board)",
"FAS (Free Alongside Ship)",
"CPT (Carriage Paid To)",
"CIP (Carriage and Insurance Paid to)",
"CFR (Cost and Freight)",
"DPU (Delivered At Place Unloaded)",
"DAP (Delivered At Place)",
"DDP (Delivered Duty Paid)",
]
shipment = frappe.qb.DocType("Shipment")
for old_value in OLD_VALUES:
frappe.qb.update(shipment).set(shipment.incoterm, old_value[:3]).where(
shipment.incoterm == old_value
).run()

View File

@@ -25,5 +25,21 @@ def execute():
).where(
purchase_invoice.docstatus == 1
).run()
purchase_order = frappe.qb.DocType("Purchase Order")
frappe.qb.update(purchase_order).set(
purchase_order.tax_withholding_net_total, purchase_order.net_total
).set(
purchase_order.base_tax_withholding_net_total, purchase_order.base_net_total
).where(
purchase_order.company == company.name
).where(
purchase_order.apply_tds == 1
).where(
purchase_order.transaction_date >= fiscal_year_details.year_start_date
).where(
purchase_order.docstatus == 1
).run()
except FiscalYearError:
pass

View File

@@ -48,6 +48,7 @@
"tax_category",
"column_break_34",
"shipping_rule",
"incoterm",
"section_break_36",
"taxes",
"section_break_39",
@@ -1052,13 +1053,19 @@
{
"fieldname": "column_break_108",
"fieldtype": "Column Break"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-shopping-cart",
"idx": 82,
"is_submittable": 1,
"links": [],
"modified": "2022-10-11 13:06:33.479650",
"modified": "2022-11-17 17:20:54.984348",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",

View File

@@ -63,6 +63,7 @@
"tax_category",
"column_break_49",
"shipping_rule",
"incoterm",
"section_break_40",
"taxes",
"section_break_43",
@@ -1623,13 +1624,19 @@
{
"fieldname": "column_break_152",
"fieldtype": "Column Break"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2022-10-11 13:06:10.469796",
"modified": "2022-11-17 17:22:00.413878",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Incoterm", {
// refresh(frm) {
// },
// });

View File

@@ -0,0 +1,168 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:code",
"creation": "2022-11-17 15:17:34.717467",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"code",
"title",
"description"
],
"fields": [
{
"fieldname": "code",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Code",
"length": 3,
"reqd": 1,
"unique": 1
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "description",
"fieldtype": "Long Text",
"label": "Description"
}
],
"links": [
{
"group": "Selling",
"link_doctype": "Quotation",
"link_fieldname": "incoterm"
},
{
"group": "Selling",
"link_doctype": "Sales Order",
"link_fieldname": "incoterm"
},
{
"group": "Buying",
"link_doctype": "Request for Quotation",
"link_fieldname": "incoterm"
},
{
"group": "Buying",
"link_doctype": "Supplier Quotation",
"link_fieldname": "incoterm"
},
{
"group": "Buying",
"link_doctype": "Purchase Order",
"link_fieldname": "incoterm"
},
{
"group": "Stock",
"link_doctype": "Delivery Note",
"link_fieldname": "incoterm"
},
{
"group": "Stock",
"link_doctype": "Purchase Receipt",
"link_fieldname": "incoterm"
},
{
"group": "Stock",
"link_doctype": "Shipment",
"link_fieldname": "incoterm"
},
{
"group": "Accounts",
"link_doctype": "Sales Invoice",
"link_fieldname": "incoterm"
},
{
"group": "Accounts",
"link_doctype": "Purchase Invoice",
"link_fieldname": "incoterm"
}
],
"modified": "2022-11-17 22:35:52.084553",
"modified_by": "Administrator",
"module": "Setup",
"name": "Incoterm",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Purchase User"
},
{
"read": 1,
"role": "Sales User"
},
{
"read": 1,
"role": "Accounts User"
},
{
"read": 1,
"role": "Stock User"
}
],
"show_title_field_in_link": 1,
"sort_field": "name",
"sort_order": "ASC",
"states": [],
"title_field": "title",
"translated_doctype": 1
}

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class Incoterm(Document):
pass
def create_incoterms():
"""Create Incoterm records from incoterms.csv."""
import os
from csv import DictReader
with open(os.path.join(os.path.dirname(__file__), "incoterms.csv"), "r") as f:
for incoterm in DictReader(f):
if frappe.db.exists("Incoterm", incoterm["code"]):
continue
doc = frappe.new_doc("Incoterm")
doc.update(incoterm)
doc.save()

View File

@@ -0,0 +1,12 @@
code,title
EXW,Ex Works
FCA,Free Carrier
FAS,Free Alongside Ship
FOB,Free On Board
CPT,Carriage Paid To
CIP,Carriage and Insurance Paid to
CFR,Cost and Freight
CIF,"Cost, Insurance and Freight"
DAP,Delivered At Place
DPU,Delivered At Place Unloaded
DDP,Delivered Duty Paid
1 code title
2 EXW Ex Works
3 FCA Free Carrier
4 FAS Free Alongside Ship
5 FOB Free On Board
6 CPT Carriage Paid To
7 CIP Carriage and Insurance Paid to
8 CFR Cost and Freight
9 CIF Cost, Insurance and Freight
10 DAP Delivered At Place
11 DPU Delivered At Place Unloaded
12 DDP Delivered Duty Paid

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestIncoterm(FrappeTestCase):
pass

View File

@@ -10,6 +10,7 @@ from frappe.utils import cint
from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import DEFAULT_MAPPERS
from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
from erpnext.setup.doctype.incoterm.incoterm import create_incoterms
from .default_success_action import get_default_success_action
@@ -25,6 +26,7 @@ def after_install():
create_default_cash_flow_mapper_templates()
create_default_success_action()
create_default_energy_point_rules()
create_incoterms()
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()

File diff suppressed because it is too large Load Diff

View File

@@ -62,6 +62,7 @@
"tax_category",
"column_break_39",
"shipping_rule",
"incoterm",
"section_break_41",
"taxes",
"section_break_44",
@@ -1381,13 +1382,19 @@
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2022-10-11 13:06:58.655635",
"modified": "2022-11-17 17:22:42.860790",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -71,6 +71,8 @@ frappe.ui.form.on('Inventory Dimension', {
if (r.message && r.message.length) {
frm.set_df_property("fetch_from_parent", "options",
[""].concat(r.message));
} else {
frm.set_df_property("fetch_from_parent", "hidden", 1);
}
}
});

View File

@@ -11,20 +11,20 @@
"reference_document",
"column_break_4",
"disabled",
"section_break_7",
"field_mapping_section",
"source_fieldname",
"column_break_9",
"target_fieldname",
"applicable_for_documents_tab",
"apply_to_all_doctypes",
"column_break_13",
"document_type",
"istable",
"type_of_transaction",
"fetch_from_parent",
"column_break_16",
"condition",
"istable",
"applicable_condition_example_section",
"condition",
"conditional_rule_examples_section",
"html_19"
],
"fields": [
@@ -52,13 +52,13 @@
{
"fieldname": "applicable_for_documents_tab",
"fieldtype": "Tab Break",
"label": "Applicable For Documents"
"label": "Applicable For"
},
{
"depends_on": "eval:!doc.apply_to_all_doctypes",
"fieldname": "document_type",
"fieldtype": "Link",
"label": "Applicable to Document",
"label": "Apply to Document",
"mandatory_depends_on": "eval:!doc.apply_to_all_doctypes",
"options": "DocType"
},
@@ -72,6 +72,7 @@
"fetch_from": "document_type.istable",
"fieldname": "istable",
"fieldtype": "Check",
"hidden": 1,
"label": " Is Child Table",
"read_only": 1
},
@@ -79,13 +80,13 @@
"depends_on": "eval:!doc.apply_to_all_doctypes",
"fieldname": "condition",
"fieldtype": "Code",
"label": "Applicable Condition"
"label": "Conditional Rule"
},
{
"default": "0",
"default": "1",
"fieldname": "apply_to_all_doctypes",
"fieldtype": "Check",
"label": "Apply to All Inventory Document Types"
"label": "Apply to All Inventory Documents"
},
{
"default": "0",
@@ -93,10 +94,6 @@
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"fieldname": "target_fieldname",
"fieldtype": "Data",
@@ -115,13 +112,11 @@
"collapsible": 1,
"fieldname": "field_mapping_section",
"fieldtype": "Section Break",
"hidden": 1,
"label": "Field Mapping"
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.apply_to_all_doctypes",
"fieldname": "type_of_transaction",
"fieldtype": "Select",
"label": "Type of Transaction",
@@ -136,23 +131,33 @@
"collapsible": 1,
"depends_on": "eval:!doc.apply_to_all_doctypes",
"fieldname": "applicable_condition_example_section",
"fieldtype": "Section Break",
"label": "Applicable Condition Examples"
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"description": "Set fieldname or DocType name like Supplier, Customer etc.",
"depends_on": "eval:!doc.apply_to_all_doctypes",
"description": "Set fieldname from which you want to fetch the data from the parent form.",
"fieldname": "fetch_from_parent",
"fieldtype": "Select",
"label": "Fetch Value From Parent Form"
"label": "Fetch Value From"
},
{
"fieldname": "column_break_13",
"fieldtype": "Section Break"
},
{
"depends_on": "eval:!doc.apply_to_all_doctypes",
"fieldname": "conditional_rule_examples_section",
"fieldtype": "Section Break",
"label": "Conditional Rule Examples"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-09-02 13:29:04.098469",
"modified": "2022-11-15 15:50:16.767105",
"modified_by": "Administrator",
"module": "Stock",
"name": "Inventory Dimension",

View File

@@ -33,10 +33,22 @@ class InventoryDimension(Document):
)
def validate(self):
self.validate_reference_document()
def before_save(self):
self.do_not_update_document()
self.reset_value()
self.validate_reference_document()
self.set_source_and_target_fieldname()
self.set_type_of_transaction()
self.set_fetch_value_from()
def set_type_of_transaction(self):
if self.apply_to_all_doctypes:
self.type_of_transaction = "Both"
def set_fetch_value_from(self):
if self.apply_to_all_doctypes:
self.fetch_from_parent = self.reference_document
def do_not_update_document(self):
if self.is_new() or not self.has_stock_ledger():

View File

@@ -140,14 +140,13 @@ class TestInventoryDimension(FrappeTestCase):
self.assertRaises(DoNotChangeError, inv_dim1.save)
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
create_inventory_dimension(
reference_document="Rack",
type_of_transaction="Both",
dimension_name="Rack",
apply_to_all_doctypes=1,
fetch_from_parent="Rack",
inv_dimension = create_inventory_dimension(
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
)
self.assertEqual(inv_dimension.type_of_transaction, "Both")
self.assertEqual(inv_dimension.fetch_from_parent, "Rack")
create_custom_field(
"Purchase Receipt", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
)

View File

@@ -370,9 +370,6 @@ frappe.ui.form.on("Material Request Item", {
if (flt(d.qty) < flt(d.min_order_qty)) {
frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
}
const item = locals[doctype][name];
frm.events.get_item_data(frm, item, false);
},
from_warehouse: function(frm, doctype, name) {

View File

@@ -63,6 +63,7 @@
"tax_category",
"column_break_53",
"shipping_rule",
"incoterm",
"taxes_section",
"taxes",
"totals",
@@ -1218,13 +1219,19 @@
{
"fieldname": "column_break_104",
"fieldtype": "Column Break"
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2022-10-11 13:02:31.776256",
"modified": "2022-11-17 17:29:30.067536",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",

View File

@@ -34,6 +34,22 @@ frappe.ui.form.on('Repost Item Valuation', {
frm.trigger('setup_realtime_progress');
},
based_on: function(frm) {
var fields_to_reset = [];
if (frm.doc.based_on == 'Transaction') {
fields_to_reset = ['item_code', 'warehouse'];
} else if (frm.doc.based_on == 'Item and Warehouse') {
fields_to_reset = ['voucher_type', 'voucher_no'];
}
if (fields_to_reset) {
fields_to_reset.forEach(field => {
frm.set_value(field, undefined);
});
}
},
setup_realtime_progress: function(frm) {
frappe.realtime.on('item_reposting_progress', data => {
if (frm.doc.name !== data.name) {

View File

@@ -50,13 +50,15 @@
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date",
"read_only_depends_on": "eval: doc.based_on == \"Transaction\"",
"reqd": 1
},
{
"fetch_from": "voucher_no.posting_time",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time"
"label": "Posting Time",
"read_only_depends_on": "eval: doc.based_on == \"Transaction\""
},
{
"default": "Queued",
@@ -195,7 +197,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-06-13 12:20:22.182322",
"modified": "2022-11-28 16:00:05.637440",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",

View File

@@ -5,7 +5,7 @@ import frappe
from frappe import _
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
from frappe.model.document import Document
from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime
from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime
from frappe.utils.user import get_users_with_role
from rq.timeouts import JobTimeoutException
@@ -25,6 +25,27 @@ class RepostItemValuation(Document):
self.set_status(write=False)
self.reset_field_values()
self.set_company()
self.validate_accounts_freeze()
def validate_accounts_freeze(self):
acc_settings = frappe.db.get_value(
"Accounts Settings",
"Accounts Settings",
["acc_frozen_upto", "frozen_accounts_modifier"],
as_dict=1,
)
if not acc_settings.acc_frozen_upto:
return
if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto):
if (
acc_settings.frozen_accounts_modifier
and frappe.session.user in get_users_with_role(acc_settings.frozen_accounts_modifier)
):
frappe.msgprint(_("Caution: This might alter frozen accounts."))
return
frappe.throw(
_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto)
)
def reset_field_values(self):
if self.based_on == "Transaction":
@@ -240,7 +261,7 @@ def _get_directly_dependent_vouchers(doc):
def notify_error_to_stock_managers(doc, traceback):
recipients = get_users_with_role("Stock Manager")
if not recipients:
get_users_with_role("System Manager")
recipients = get_users_with_role("System Manager")
subject = _("Error while reposting item valuation")
message = (

View File

@@ -327,3 +327,26 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
# outstanding should not be affected
sinv.reload()
self.assertEqual(sinv.outstanding_amount, 100)
def test_account_freeze_validation(self):
today = nowdate()
riv = frappe.get_doc(
doctype="Repost Item Valuation",
item_code="_Test Item",
warehouse="_Test Warehouse - _TC",
based_on="Item and Warehouse",
posting_date=today,
posting_time="00:01:00",
)
riv.flags.dont_run_in_test = True # keep it queued
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.acc_frozen_upto = today
accounts_settings.frozen_accounts_modifier = ""
accounts_settings.save()
self.assertRaises(frappe.ValidationError, riv.save)
accounts_settings.acc_frozen_upto = ""
accounts_settings.save()

View File

@@ -412,9 +412,9 @@
},
{
"fieldname": "incoterm",
"fieldtype": "Select",
"fieldtype": "Link",
"label": "Incoterm",
"options": "EXW (Ex Works)\nFCA (Free Carrier)\nCPT (Carriage Paid To)\nCIP (Carriage and Insurance Paid to)\nDPU (Delivered At Place Unloaded)\nDAP (Delivered At Place)\nDDP (Delivered Duty Paid)"
"options": "Incoterm"
},
{
"fieldname": "shipment_delivery_note",
@@ -433,10 +433,11 @@
],
"is_submittable": 1,
"links": [],
"modified": "2021-04-13 17:14:18.181818",
"modified": "2022-11-17 17:23:27.025802",
"modified_by": "Administrator",
"module": "Stock",
"name": "Shipment",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -470,5 +471,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -1079,7 +1079,8 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
if (frm.doc.purpose === 'Material Receipt') return;
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
new erpnext.SerialNoBatchSelector({
if (frm.batch_selector?.dialog?.display) return;
frm.batch_selector = new erpnext.SerialNoBatchSelector({
frm: frm,
item: item,
warehouse_details: get_warehouse_type_and_name(item),

View File

@@ -230,7 +230,7 @@ class StockReconciliation(StockController):
if item.has_serial_no or item.has_batch_no:
has_serial_no = True
self.get_sle_for_serialized_items(row, sl_entries)
self.get_sle_for_serialized_items(row, sl_entries, item)
else:
if row.serial_no or row.batch_no:
frappe.throw(
@@ -282,7 +282,7 @@ class StockReconciliation(StockController):
if has_serial_no and sl_entries:
self.update_valuation_rate_for_serial_no()
def get_sle_for_serialized_items(self, row, sl_entries):
def get_sle_for_serialized_items(self, row, sl_entries, item):
from erpnext.stock.stock_ledger import get_previous_sle
serial_nos = get_serial_nos(row.serial_no)
@@ -348,6 +348,9 @@ class StockReconciliation(StockController):
if row.qty:
args = self.get_sle_for_items(row)
if item.has_serial_no and item.has_batch_no:
args["qty_after_transaction"] = row.qty
args.update(
{
"actual_qty": row.qty,

View File

@@ -644,6 +644,38 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
)
self.assertEqual(len(active_sr_no), 0)
def test_serial_no_batch_no_item(self):
item = self.make_item(
"Test Serial No Batch No Item",
{
"is_stock_item": 1,
"has_serial_no": 1,
"has_batch_no": 1,
"serial_no_series": "SRS9.####",
"batch_number_series": "BNS9.####",
"create_new_batch": 1,
},
)
warehouse = "_Test Warehouse - _TC"
sr = create_stock_reconciliation(
item_code=item.name,
warehouse=warehouse,
qty=1,
rate=100,
)
sl_entry = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Stock Reconciliation", "voucher_no": sr.name},
["actual_qty", "qty_after_transaction"],
as_dict=1,
)
self.assertEqual(flt(sl_entry.actual_qty), 1.0)
self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0)
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)

View File

@@ -58,6 +58,12 @@ def execute(filters=None):
if sle.serial_no:
update_available_serial_nos(available_serial_nos, sle)
if sle.actual_qty:
sle["in_out_rate"] = flt(sle.stock_value_difference / sle.actual_qty, precision)
elif sle.voucher_type == "Stock Reconciliation":
sle["in_out_rate"] = sle.valuation_rate
data.append(sle)
if include_uom:
@@ -185,10 +191,18 @@ def get_columns(filters):
"convertible": "rate",
},
{
"label": _("Valuation Rate"),
"label": _("Avg Rate (Balance Stock)"),
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"width": 110,
"width": 180,
"options": "Company:company:default_currency",
"convertible": "rate",
},
{
"label": _("Valuation Rate"),
"fieldname": "in_out_rate",
"fieldtype": "Currency",
"width": 140,
"options": "Company:company:default_currency",
"convertible": "rate",
},

View File

@@ -263,8 +263,8 @@ def repost_future_sle(
def validate_item_warehouse(args):
for field in ["item_code", "warehouse", "posting_date", "posting_time"]:
if not args.get(field):
validation_msg = f"The field {frappe.unscrub(args.get(field))} is required for the reposting"
if args.get(field) in [None, ""]:
validation_msg = f"The field {frappe.unscrub(field)} is required for the reposting"
frappe.throw(_(validation_msg))

View File

@@ -32,7 +32,7 @@
<div class="product-container mt-4 {{ padding_top }} {{ info_col }}">
<div class="item-content {{ 'mt-minus-2' if (show_tabs and tabs) else '' }}">
<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
<div class="product-page-content">
<!-- Product Specifications Table Section -->
{% if show_tabs and tabs %}
<div class="category-tabs">

View File

@@ -9905,3 +9905,14 @@ Total Asset,Aktiva,
Total Liability,Verbindlichkeiten,
Total Equity,Eigenkapital,
Warehouse wise Stock Value,Warenwert nach Lager,
Ex Works,Ab Werk,
Free Carrier,Frei Frachtführer,
Free Alongside Ship,Frei Längsseite Schiff,
Free on Board,Frei an Bord,
Carriage Paid To,Frachtfrei,
Carriage and Insurance Paid to,Frachtfrei versichert,
Cost and Freight,Kosten und Fracht,
"Cost, Insurance and Freight","Kosten, Versicherung und Fracht",
Delivered at Place,Geliefert benannter Ort,
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
Delivered Duty Paid,Geliefert verzollt,
Can't render this file because it is too large.