[taxes and charges] refactor

This commit is contained in:
Rushabh Mehta
2015-02-25 15:08:42 +05:30
parent e91388381c
commit 2a21bc9fc2
15 changed files with 213 additions and 136 deletions

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
{% include "public/js/controllers/accounts.js" %} {% include "public/js/controllers/accounts.js" %}
frappe.ui.form.on("Purchase Taxes and Charges", "add_deduct_tax", function(doc, cdt, cdn) { frappe.ui.form.on("Purchase Taxes and Charges", "add_deduct_tax", function(doc, cdt, cdn) {

View File

@@ -199,8 +199,8 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.hide_fields(this.frm.doc); this.hide_fields(this.frm.doc);
}, },
items_on_form_rendered: function(doc, grid_row) { items_on_form_rendered: function() {
erpnext.setup_serial_no(grid_row) erpnext.setup_serial_no();
} }
}); });

View File

@@ -56,7 +56,7 @@
"oldfieldname": "rate", "oldfieldname": "rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "permlevel": 0,
"reqd": 1 "reqd": 0
}, },
{ {
"fieldname": "col_break_1", "fieldname": "col_break_1",
@@ -186,7 +186,7 @@
"hide_heading": 1, "hide_heading": 1,
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2015-02-23 12:36:02.213508", "modified": "2015-02-25 02:50:44.152307",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Taxes and Charges", "name": "Sales Taxes and Charges",

View File

@@ -1,9 +1,10 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
cur_frm.cscript.tax_table = "Sales Taxes and Charges";
{% include "public/js/controllers/accounts.js" %} {% include "public/js/controllers/accounts.js" %}
cur_frm.cscript.onload = function(doc, cdt, cdn) { frappe.ui.form.on("Sales Taxes and Charges Master", "onload", function(frm) {
if(doc.doctype === "Sales Taxes and Charges Master") erpnext.add_applicable_territory();
erpnext.add_applicable_territory(); });
}

View File

@@ -20,3 +20,4 @@ class SalesTaxesandChargesMaster(Document):
validate_taxes_and_charges(tax) validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, self) validate_inclusive_tax(tax, self)

View File

@@ -370,6 +370,15 @@ def validate_taxes_and_charges(tax):
elif tax.row_id and cint(tax.row_id) >= cint(tax.idx): elif tax.row_id and cint(tax.row_id) >= cint(tax.idx):
frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type")) frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type"))
if tax.charge_type == "Actual":
if not tax.tax_amount:
frappe.throw(_("Amount is mandatory for charge type 'Actual'"))
tax.rate = None
else:
if not tax.rate:
frappe.throw(_("Rate is mandatory for charge type '{0}'").format(tax.charge_type))
tax.tax_amount = None
def validate_inclusive_tax(tax, doc): def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range): def _on_previous_row_error(row_range):
throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx,

View File

@@ -126,3 +126,4 @@ erpnext.patches.v5_0.item_patches
erpnext.patches.v5_0.update_journal_entry_title erpnext.patches.v5_0.update_journal_entry_title
erpnext.patches.v5_0.taxes_and_totals_in_party_currency erpnext.patches.v5_0.taxes_and_totals_in_party_currency
erpnext.patches.v5_0.replace_renamed_fields_in_custom_scripts_and_print_formats erpnext.patches.v5_0.replace_renamed_fields_in_custom_scripts_and_print_formats
execute:frappe.db.sql("update `tabStock Entry` set from_bom = if(ifnull(bom_no, '')='', 0, 1)")

View File

@@ -2,6 +2,10 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
// get tax rate // get tax rate
frappe.provide("erpnext.taxes");
frappe.provide("erpnext.taxes.flags");
cur_frm.cscript.account_head = function(doc, cdt, cdn) { cur_frm.cscript.account_head = function(doc, cdt, cdn) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if(!d.charge_type && d.account_head){ if(!d.charge_type && d.account_head){
@@ -82,34 +86,60 @@ cur_frm.cscript.validate_inclusive_tax = function(tax) {
} }
} }
frappe.ui.form.on(cur_frm.cscript.tax_table, "row_id", function(frm, cdt, cdn) { if(!erpnext.taxes.flags[cur_frm.cscript.tax_table]) {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn); erpnext.taxes.flags[cur_frm.cscript.tax_table] = true;
});
frappe.ui.form.on(cur_frm.cscript.tax_table, "rate", function(frm, cdt, cdn) { frappe.ui.form.on(cur_frm.cscript.tax_table, "row_id", function(frm, cdt, cdn) {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
});
frappe.ui.form.on(cur_frm.cscript.tax_table, "tax_amount", function(frm, cdt, cdn) {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
});
frappe.ui.form.on(cur_frm.cscript.tax_table, "charge_type", function(frm, cdt, cdn) {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
});
frappe.ui.form.on(cur_frm.cscript.tax_table, "included_in_print_rate", function(frm, cdt, cdn) {
var tax = frappe.get_doc(cdt, cdn);
try {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn); cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
cur_frm.cscript.validate_inclusive_tax(tax); });
} catch(e) {
tax.included_in_print_rate = 0; frappe.ui.form.on(cur_frm.cscript.tax_table, "rate", function(frm, cdt, cdn) {
refresh_field("included_in_print_rate", tax.name, tax.parentfield); cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
throw e; });
frappe.ui.form.on(cur_frm.cscript.tax_table, "tax_amount", function(frm, cdt, cdn) {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
});
frappe.ui.form.on(cur_frm.cscript.tax_table, "charge_type", function(frm, cdt, cdn) {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
erpnext.taxes.set_conditional_mandatory_rate_or_amount(frm);
});
frappe.ui.form.on(cur_frm.cscript.tax_table, "included_in_print_rate", function(frm, cdt, cdn) {
var tax = frappe.get_doc(cdt, cdn);
try {
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
cur_frm.cscript.validate_inclusive_tax(tax);
} catch(e) {
tax.included_in_print_rate = 0;
refresh_field("included_in_print_rate", tax.name, tax.parentfield);
throw e;
}
});
}
erpnext.taxes.set_conditional_mandatory_rate_or_amount = function(frm) {
var grid_row = frm.open_grid_row();
if(grid_row.doc.charge_type==="Actual") {
grid_row.toggle_display("tax_amount", true);
grid_row.toggle_reqd("tax_amount", true);
grid_row.toggle_display("rate", false);
grid_row.toggle_reqd("rate", false);
} else {
grid_row.toggle_display("rate", true);
grid_row.toggle_reqd("rate", true);
grid_row.toggle_display("tax_amount", false);
grid_row.toggle_reqd("tax_amount", false);
} }
}
// setup conditional mandatory for tax and rates
frappe.ui.form.on(cur_frm.doctype, "taxes_on_form_rendered", function(frm) {
erpnext.taxes.set_conditional_mandatory_rate_or_amount(frm);
}); });
cur_frm.set_query("account_head", "taxes", function(doc) { cur_frm.set_query("account_head", "taxes", function(doc) {
if(cur_frm.cscript.tax_table == "Sales Taxes and Charges") { if(cur_frm.cscript.tax_table == "Sales Taxes and Charges") {
var account_type = ["Tax", "Chargeable", "Expense Account"]; var account_type = ["Tax", "Chargeable", "Expense Account"];

View File

@@ -57,13 +57,15 @@ $.extend(erpnext, {
} }
}, },
setup_serial_no: function(grid_row) { setup_serial_no: function() {
var grid_row = cur_frm.open_grid_row();
if(!grid_row.fields_dict.serial_no || if(!grid_row.fields_dict.serial_no ||
grid_row.fields_dict.serial_no.get_status()!=="Write") return; grid_row.fields_dict.serial_no.get_status()!=="Write") return;
var $btn = $('<button class="btn btn-sm btn-default">'+__("Add Serial No")+'</button>') var $btn = $('<button class="btn btn-sm btn-default">'+__("Add Serial No")+'</button>')
.appendTo($("<div>") .appendTo($("<div>")
.css({"margin-bottom": "10px", "margin-left": "15px"}) .css({"margin-bottom": "10px", "margin-top": "10px"})
.appendTo(grid_row.fields_dict.serial_no.$wrapper)); .appendTo(grid_row.fields_dict.serial_no.$wrapper));
$btn.on("click", function() { $btn.on("click", function() {
@@ -99,7 +101,7 @@ $.extend(erpnext, {
d.show(); d.show();
}); });
}, },
get_letter_head: function(company) { get_letter_head: function(company) {
frappe.call({ frappe.call({
type:"GET", type:"GET",
@@ -112,7 +114,7 @@ $.extend(erpnext, {
} }
}); });
}, },
}); });

View File

@@ -79,7 +79,7 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
}, },
items_on_form_rendered: function(doc, grid_row) { items_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row) erpnext.setup_serial_no();
} }
}); });

View File

@@ -274,7 +274,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}, },
items_on_form_rendered: function(doc, grid_row) { items_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row) erpnext.setup_serial_no();
}, },
customer: function() { customer: function() {

View File

@@ -7,6 +7,14 @@
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"label": "",
"oldfieldtype": "Section Break",
"permlevel": 0,
"read_only": 0
},
{ {
"fieldname": "col1", "fieldname": "col1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
@@ -73,15 +81,6 @@
"reqd": 0, "reqd": 0,
"search_index": 1 "search_index": 1
}, },
{
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"fieldname": "bom_no",
"fieldtype": "Link",
"label": "BOM No",
"options": "BOM",
"permlevel": 0,
"read_only": 0
},
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"depends_on": "eval:doc.purpose==\"Sales Return\"", "depends_on": "eval:doc.purpose==\"Sales Return\"",
@@ -132,6 +131,13 @@
"reqd": 0, "reqd": 0,
"search_index": 1 "search_index": 1
}, },
{
"fieldname": "from_bom",
"fieldtype": "Check",
"label": "From BOM",
"permlevel": 0,
"precision": ""
},
{ {
"fieldname": "col2", "fieldname": "col2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
@@ -178,13 +184,91 @@
"search_index": 0 "search_index": 0
}, },
{ {
"fieldname": "items_section", "depends_on": "eval: doc.from_bom && (doc.purpose!==\"Sales Return\" && doc.purpose!==\"Purchase Return\")",
"fieldname": "sb1",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "", "label": "",
"oldfieldtype": "Section Break",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"fieldname": "bom_no",
"fieldtype": "Link",
"label": "BOM No",
"options": "BOM",
"permlevel": 0,
"read_only": 0
},
{
"depends_on": "eval:inList([\"Manufacture\", \"Repack\"], doc.purpose)",
"fieldname": "additional_operating_cost",
"fieldtype": "Currency",
"label": "Additional Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"read_only": 0
},
{
"fieldname": "cb1",
"fieldtype": "Column Break",
"permlevel": 0,
"read_only": 0
},
{
"allow_on_submit": 0,
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"description": "As per Stock UOM",
"fieldname": "fg_completed_qty",
"fieldtype": "Float",
"hidden": 0,
"in_filter": 0,
"label": "Manufacturing Quantity",
"no_copy": 0,
"oldfieldname": "fg_completed_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
},
{
"default": "1",
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"description": "Including items for sub assemblies",
"fieldname": "use_multi_level_bom",
"fieldtype": "Check",
"label": "Use Multi-Level BOM",
"permlevel": 0,
"print_hide": 1,
"read_only": 0
},
{
"allow_on_submit": 0,
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"fieldname": "get_items",
"fieldtype": "Button",
"hidden": 0,
"in_filter": 0,
"label": "Get Items",
"no_copy": 0,
"oldfieldtype": "Button",
"permlevel": 0,
"print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
},
{
"fieldname": "section_break_12",
"fieldtype": "Section Break",
"permlevel": 0,
"precision": ""
},
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"fieldname": "from_warehouse", "fieldname": "from_warehouse",
@@ -301,77 +385,6 @@
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{
"depends_on": "eval:(doc.purpose!==\"Sales Return\" && doc.purpose!==\"Purchase Return\")",
"fieldname": "sb1",
"fieldtype": "Section Break",
"label": "From Bill of Materials",
"permlevel": 0,
"read_only": 0
},
{
"allow_on_submit": 0,
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"description": "As per Stock UOM",
"fieldname": "fg_completed_qty",
"fieldtype": "Float",
"hidden": 0,
"in_filter": 0,
"label": "Manufacturing Quantity",
"no_copy": 0,
"oldfieldname": "fg_completed_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
},
{
"depends_on": "eval:inList([\"Manufacture\", \"Repack\"], doc.purpose)",
"fieldname": "additional_operating_cost",
"fieldtype": "Currency",
"label": "Additional Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"read_only": 0
},
{
"fieldname": "cb1",
"fieldtype": "Column Break",
"permlevel": 0,
"read_only": 0
},
{
"default": "1",
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.",
"fieldname": "use_multi_level_bom",
"fieldtype": "Check",
"label": "Use Multi-Level BOM",
"permlevel": 0,
"print_hide": 1,
"read_only": 0
},
{
"allow_on_submit": 0,
"depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)",
"fieldname": "get_items",
"fieldtype": "Button",
"hidden": 0,
"in_filter": 0,
"label": "Get Items",
"no_copy": 0,
"oldfieldtype": "Button",
"permlevel": 0,
"print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
},
{ {
"fieldname": "fold", "fieldname": "fold",
"fieldtype": "Fold", "fieldtype": "Fold",
@@ -632,7 +645,7 @@
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "issingle": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2015-02-20 05:04:09.060180", "modified": "2015-02-25 01:59:14.371042",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry", "name": "Stock Entry",

View File

@@ -282,14 +282,15 @@ class StockEntry(StockController):
def add_operation_cost(self, raw_material_cost, force): def add_operation_cost(self, raw_material_cost, force):
"""Adds operating cost if Production Order is set""" """Adds operating cost if Production Order is set"""
# set incoming rate for fg item # set incoming rate for fg item
if self.production_order: number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse]) for d in self.get("items"):
for d in self.get("items"): if (d.t_warehouse and number_of_fg_items == 1):
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1): operation_cost_per_unit = 0.0
if self.production_order:
operation_cost_per_unit = self.get_operation_cost_per_unit(d.bom_no, d.qty) operation_cost_per_unit = self.get_operation_cost_per_unit(d.bom_no, d.qty)
d.incoming_rate = operation_cost_per_unit + (raw_material_cost / flt(d.transfer_qty)) d.incoming_rate = operation_cost_per_unit + (raw_material_cost / flt(d.transfer_qty))
d.amount = flt(flt(d.transfer_qty) * flt(d.incoming_rate), self.precision("transfer_qty", d)) d.amount = flt(flt(d.transfer_qty) * flt(d.incoming_rate), self.precision("transfer_qty", d))
break break
def get_operation_cost_per_unit(self, bom_no, qty): def get_operation_cost_per_unit(self, bom_no, qty):
"""Returns operating cost from Production Order for given `bom_no`""" """Returns operating cost from Production Order for given `bom_no`"""
@@ -510,10 +511,14 @@ class StockEntry(StockController):
self.set('items', []) self.set('items', [])
self.validate_production_order() self.validate_production_order()
if not getattr(self, "pro_doc", None):
self.pro_doc = None
if self.production_order: if self.production_order:
# common validations # common validations
if not getattr(self, "pro_doc", None): if not self.pro_doc:
self.pro_doc = frappe.get_doc('Production Order', self.production_order) self.pro_doc = frappe.get_doc('Production Order', self.production_order)
if self.pro_doc: if self.pro_doc:
self.bom_no = self.pro_doc.bom_no self.bom_no = self.pro_doc.bom_no
else: else:

View File

@@ -10,12 +10,18 @@ from erpnext.stock.stock_ledger import update_entries_after
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
from erpnext.stock.utils import get_stock_balance from erpnext.stock.utils import get_stock_balance
class OpeningEntryAccountError(frappe.ValidationError): pass
class StockReconciliation(StockController): class StockReconciliation(StockController):
def __init__(self, arg1, arg2=None): def __init__(self, arg1, arg2=None):
super(StockReconciliation, self).__init__(arg1, arg2) super(StockReconciliation, self).__init__(arg1, arg2)
self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"] self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]
def validate(self): def validate(self):
if not self.expense_account:
self.expense_account = frappe.db.get_value("Company", self.company, "stock_adjustment_account")
if not self.cost_center:
self.cost_center = frappe.db.get_value("Company", self.company, "cost_center")
self.validate_posting_time() self.validate_posting_time()
self.remove_items_with_no_change() self.remove_items_with_no_change()
self.validate_data() self.validate_data()
@@ -215,7 +221,12 @@ class StockReconciliation(StockController):
msgprint(_("Please enter Expense Account"), raise_exception=1) msgprint(_("Please enter Expense Account"), raise_exception=1)
elif not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""): elif not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""):
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss": if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a 'Liability' type account, since this Stock Reconciliation is an Opening Entry")) frappe.throw(_("Difference Account must be a 'Liability' type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)
def get_items_for(self, warehouse):
self.items = []
for item in get_items(warehouse, self.posting_date, self.posting_time):
self.append("items", item)
@frappe.whitelist() @frappe.whitelist()
def get_items(warehouse, posting_date, posting_time): def get_items(warehouse, posting_date, posting_time):

View File

@@ -150,7 +150,7 @@ class update_entries_after(object):
return return
if sle.serial_no: if sle.serial_no:
self.valuation_rate = self.get_serialized_values(sle) self.get_serialized_values(sle)
self.qty_after_transaction += flt(sle.actual_qty) self.qty_after_transaction += flt(sle.actual_qty)
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate) self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
else: else:
@@ -208,12 +208,14 @@ class update_entries_after(object):
if incoming_rate < 0: if incoming_rate < 0:
# wrong incoming rate # wrong incoming rate
incoming_rate = self.valuation_rate incoming_rate = self.valuation_rate
elif incoming_rate == 0 or flt(sle.actual_qty) < 0:
# In case of delivery/stock issue, get average purchase rate elif incoming_rate == 0:
# of serial nos of current entry if flt(sle.actual_qty) < 0:
incoming_rate = flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) # In case of delivery/stock issue, get average purchase rate
from `tabSerial No` where name in (%s)""" % (", ".join(["%s"]*len(serial_no))), # of serial nos of current entry
tuple(serial_no))[0][0]) incoming_rate = flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0))
from `tabSerial No` where name in (%s)""" % (", ".join(["%s"]*len(serial_no))),
tuple(serial_no))[0][0])
if incoming_rate and not self.valuation_rate: if incoming_rate and not self.valuation_rate:
self.valuation_rate = incoming_rate self.valuation_rate = incoming_rate