Merge branch 'develop' into payment_entry_validations_and_trigger_develop

This commit is contained in:
Deepesh Garg 2021-08-11 18:20:42 +05:30 committed by GitHub
commit 13fb71f642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 981 additions and 432 deletions

View File

@ -230,7 +230,7 @@ class Account(NestedSet):
if self.check_gle_exists(): if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group.")) throw(_("Account with existing transaction can not be converted to group."))
elif self.account_type and not self.flags.exclude_account_type_check: elif self.account_type and not self.flags.exclude_account_type_check:
throw(_("Cannot covert to Group because Account Type is selected.")) throw(_("Cannot convert to Group because Account Type is selected."))
else: else:
self.is_group = 1 self.is_group = 1
self.save() self.save()

View File

@ -10,6 +10,7 @@
"accounts_transactions_settings_section", "accounts_transactions_settings_section",
"over_billing_allowance", "over_billing_allowance",
"role_allowed_to_over_bill", "role_allowed_to_over_bill",
"credit_controller",
"make_payment_via_journal_entry", "make_payment_via_journal_entry",
"column_break_11", "column_break_11",
"check_supplier_invoice_uniqueness", "check_supplier_invoice_uniqueness",
@ -27,7 +28,6 @@
"acc_frozen_upto", "acc_frozen_upto",
"frozen_accounts_modifier", "frozen_accounts_modifier",
"column_break_4", "column_break_4",
"credit_controller",
"deferred_accounting_settings_section", "deferred_accounting_settings_section",
"book_deferred_entries_based_on", "book_deferred_entries_based_on",
"column_break_18", "column_break_18",
@ -73,11 +73,10 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"description": "This role is allowed to submit transactions that exceed credit limits",
"fieldname": "credit_controller", "fieldname": "credit_controller",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Credit Controller", "label": "Role allowed to bypass Credit Limit",
"options": "Role" "options": "Role"
}, },
{ {
@ -268,7 +267,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-06-17 20:26:03.721202", "modified": "2021-08-09 13:08:01.335416",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@ -249,7 +249,7 @@ class TestBudget(unittest.TestCase):
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "project": if budget_against_field == "project":
budget_against = "_Test Project" budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"})
else: else:
budget_against = budget_against_CC or "_Test Cost Center - _TC" budget_against = budget_against_CC or "_Test Cost Center - _TC"
@ -275,7 +275,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
elif budget_against_field == "project": elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate()) "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate())
def make_budget(**args): def make_budget(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@ -58,8 +58,8 @@ class GLEntry(Document):
if not self.get(k): if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k)))) frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if not (self.party_type and self.party): if not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type == "Receivable": if account_type == "Receivable":
frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}") frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
.format(self.voucher_type, self.voucher_no, self.account)) .format(self.voucher_type, self.voucher_no, self.account))
@ -73,15 +73,19 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account)) .format(self.voucher_type, self.voucher_no, self.account))
def pl_must_have_cost_center(self): def pl_must_have_cost_center(self):
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": """Validate that profit and loss type account GL entries have a cost center."""
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
self.voucher_type, self.voucher_no, self.account)
msg += " "
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
self.voucher_type)
frappe.throw(msg, title=_("Missing Cost Center")) if self.cost_center or self.voucher_type == 'Period Closing Voucher':
return
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
self.voucher_type, self.voucher_no, self.account)
msg += " "
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
self.voucher_type)
frappe.throw(msg, title=_("Missing Cost Center"))
def validate_dimensions_for_pl_and_bs(self): def validate_dimensions_for_pl_and_bs(self):
account_type = frappe.db.get_value("Account", self.account, "report_type") account_type = frappe.db.get_value("Account", self.account, "report_type")

View File

@ -558,7 +558,8 @@
"description": "Simple Python Expression, Example: territory != 'All Territories'", "description": "Simple Python Expression, Example: territory != 'All Territories'",
"fieldname": "condition", "fieldname": "condition",
"fieldtype": "Code", "fieldtype": "Code",
"label": "Condition" "label": "Condition",
"options": "PythonExpression"
}, },
{ {
"fieldname": "column_break_42", "fieldname": "column_break_42",
@ -575,7 +576,7 @@
"icon": "fa fa-gift", "icon": "fa fa-gift",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2021-03-06 22:01:24.840422", "modified": "2021-08-06 15:10:04.219321",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Pricing Rule", "name": "Pricing Rule",

View File

@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}, },
get_query_filters: { get_query_filters: {
docstatus: 1, docstatus: 1,
status: ["not in", ["Closed", "Completed"]], status: ["not in", ["Closed", "Completed", "Return Issued"]],
company: me.frm.doc.company, company: me.frm.doc.company,
is_return: 0 is_return: 0
} }

View File

@ -22,7 +22,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accoun
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from six import iteritems from six import iteritems
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
unlink_inter_company_doc unlink_inter_company_doc, check_if_return_invoice_linked_with_payment_entry
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
@ -1014,6 +1014,8 @@ class PurchaseInvoice(BuyingController):
}, item=self)) }, item=self))
def on_cancel(self): def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self)
super(PurchaseInvoice, self).on_cancel() super(PurchaseInvoice, self).on_cancel()
self.check_on_hold_or_closed_status() self.check_on_hold_or_closed_status()

View File

@ -290,6 +290,8 @@ class SalesInvoice(SellingController):
self.update_time_sheet(None) self.update_time_sheet(None)
def on_cancel(self): def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self)
super(SalesInvoice, self).on_cancel() super(SalesInvoice, self).on_cancel()
self.check_sales_order_on_hold_or_close("sales_order") self.check_sales_order_on_hold_or_close("sales_order")
@ -480,7 +482,7 @@ class SalesInvoice(SellingController):
if not self.pos_profile: if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {} pos_profile = get_pos_profile(self.company) or {}
if not pos_profile: if not pos_profile:
frappe.throw(_("No POS Profile found. Please create a New POS Profile first")) return
self.pos_profile = pos_profile.get('name') self.pos_profile = pos_profile.get('name')
pos = {} pos = {}
@ -922,7 +924,7 @@ class SalesInvoice(SellingController):
asset = frappe.get_doc("Asset", item.asset) asset = frappe.get_doc("Asset", item.asset)
else: else:
frappe.throw(_( frappe.throw(_(
"Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
title=_("Missing Asset") title=_("Missing Asset")
) )
if (len(asset.finance_books) > 1 and not item.finance_book if (len(asset.finance_books) > 1 and not item.finance_book
@ -944,7 +946,7 @@ class SalesInvoice(SellingController):
gl_entries.append(self.get_gl_dict(gle, item=item)) gl_entries.append(self.get_gl_dict(gle, item=item))
self.set_asset_status(asset) self.set_asset_status(asset)
else: else:
# Do not book income for transfer within same company # Do not book income for transfer within same company
if not self.is_internal_transfer(): if not self.is_internal_transfer():
@ -973,7 +975,7 @@ class SalesInvoice(SellingController):
def set_asset_status(self, asset): def set_asset_status(self, asset):
if self.is_return: if self.is_return:
asset.set_status() asset.set_status()
else: else:
asset.set_status("Sold" if self.docstatus==1 else None) asset.set_status("Sold" if self.docstatus==1 else None)
def make_loyalty_point_redemption_gle(self, gl_entries): def make_loyalty_point_redemption_gle(self, gl_entries):
@ -1941,3 +1943,41 @@ def create_dunning(source_name, target_doc=None):
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist return doclist
def check_if_return_invoice_linked_with_payment_entry(self):
# If a Return invoice is linked with payment entry along with other invoices,
# the cancellation of the Return causes allocated amount to be greater than paid
if not frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
return
payment_entries = []
if self.is_return and self.return_against:
invoice = self.return_against
else:
invoice = self.name
payment_entries = frappe.db.sql_list("""
SELECT
t1.name
FROM
`tabPayment Entry` t1, `tabPayment Entry Reference` t2
WHERE
t1.name = t2.parent
and t1.docstatus = 1
and t2.reference_name = %s
and t2.allocated_amount < 0
""", invoice)
links_to_pe = []
if payment_entries:
for payment in payment_entries:
payment_entry = frappe.get_doc("Payment Entry", payment)
if len(payment_entry.references) > 1:
links_to_pe.append(payment_entry.name)
if links_to_pe:
payment_entries_link = [get_link_to_form('Payment Entry', name, label=name) for name in links_to_pe]
message = _("Please cancel and amend the Payment Entry")
message += " " + ", ".join(payment_entries_link) + " "
message += _("to unallocate the amount of this Return Invoice before cancelling it.")
frappe.throw(message)

View File

@ -6,7 +6,7 @@ import frappe
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax, validate_cost_center, validate_account_head
class SalesTaxesandChargesTemplate(Document): class SalesTaxesandChargesTemplate(Document):
def validate(self): def validate(self):
@ -39,6 +39,8 @@ def valdiate_taxes_and_charges_template(doc):
for tax in doc.get("taxes"): for tax in doc.get("taxes"):
validate_taxes_and_charges(tax) validate_taxes_and_charges(tax)
validate_account_head(tax, doc)
validate_cost_center(tax, doc)
validate_inclusive_tax(tax, doc) validate_inclusive_tax(tax, doc)
def validate_disabled(doc): def validate_disabled(doc):

View File

@ -8,6 +8,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 6 "rate": 6
}, },
@ -16,6 +17,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 6.36 "rate": 6.36
} }
@ -114,6 +116,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 12 "rate": 12
}, },
@ -122,6 +125,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 4 "rate": 4
} }
@ -137,6 +141,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 12 "rate": 12
}, },
@ -145,6 +150,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 4 "rate": 4
} }
@ -160,6 +166,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 12 "rate": 12
}, },
@ -168,6 +175,7 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 4 "rate": 4
} }

View File

@ -78,7 +78,7 @@
"label": "Cost" "label": "Cost"
}, },
{ {
"depends_on": "eval:doc.price_determination==\"Based on price list\"", "depends_on": "eval:doc.price_determination==\"Based On Price List\"",
"fieldname": "price_list", "fieldname": "price_list",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Price List", "label": "Price List",
@ -147,7 +147,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-06-25 10:53:44.205774", "modified": "2021-08-09 10:53:44.205774",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Subscription Plan", "name": "Subscription Plan",

View File

@ -100,8 +100,8 @@ def merge_similar_entries(gl_map, precision=None):
return merged_gl_map return merged_gl_map
def check_if_in_list(gle, gl_map, dimensions=None): def check_if_in_list(gle, gl_map, dimensions=None):
account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type', account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher',
'cost_center', 'project', 'voucher_detail_no'] 'cost_center', 'against_voucher_type', 'party_type', 'project']
if dimensions: if dimensions:
account_head_fieldnames = account_head_fieldnames + dimensions account_head_fieldnames = account_head_fieldnames + dimensions
@ -110,10 +110,12 @@ def check_if_in_list(gle, gl_map, dimensions=None):
same_head = True same_head = True
if e.account != gle.account: if e.account != gle.account:
same_head = False same_head = False
continue
for fieldname in account_head_fieldnames: for fieldname in account_head_fieldnames:
if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)): if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
same_head = False same_head = False
break
if same_head: if same_head:
return e return e
@ -143,16 +145,19 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
validate_expense_against_budget(args) validate_expense_against_budget(args)
def validate_cwip_accounts(gl_map): def validate_cwip_accounts(gl_map):
cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")) """Validate that CWIP account are not used in Journal Entry"""
if gl_map and gl_map[0].voucher_type != "Journal Entry":
return
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting"))
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount if cwip_enabled:
where account_type = 'Capital Work in Progress' and is_group=0""")] cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
where account_type = 'Capital Work in Progress' and is_group=0""")]
for entry in gl_map: for entry in gl_map:
if entry.account in cwip_accounts: if entry.account in cwip_accounts:
frappe.throw( frappe.throw(
_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) _("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
def round_off_debit_credit(gl_map): def round_off_debit_credit(gl_map):
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),

View File

@ -927,7 +927,6 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa
_delete_gl_entries(voucher_type, voucher_no) _delete_gl_entries(voucher_type, voucher_no)
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None): def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None):
future_stock_vouchers = []
values = [] values = []
condition = "" condition = ""
@ -943,30 +942,46 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
condition += " and company = %s" condition += " and company = %s"
values.append(company) values.append(company)
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
where where
timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s)
and is_cancelled = 0 and is_cancelled = 0
{condition} {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True): tuple([posting_date, posting_time] + values), as_dict=True)
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers return [(d.voucher_type, d.voucher_no) for d in future_stock_vouchers]
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
""" Get voucherwise list of GL entries.
Only fetches GLE fields required for comparing with new GLE.
Check compare_existing_and_expected_gle function below.
"""
gl_entries = {} gl_entries = {}
if future_stock_vouchers: if not future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry` return gl_entries
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))), voucher_nos = [d[1] for d in future_stock_vouchers]
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) gles = frappe.db.sql("""
select name, account, credit, debit, cost_center, project
from `tabGL Entry`
where
posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s'] * len(voucher_nos))),
tuple([posting_date] + voucher_nos), as_dict=1)
for d in gles:
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries return gl_entries
def compare_existing_and_expected_gle(existing_gle, expected_gle, precision): def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
if len(existing_gle) != len(expected_gle):
return False
matched = True matched = True
for entry in expected_gle: for entry in expected_gle:
account_existed = False account_existed = False

View File

@ -639,7 +639,7 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.available_for_use_date = '2030-06-12' asset.available_for_use_date = '2030-07-12'
asset.purchase_date = '2030-01-01' asset.purchase_date = '2030-01-01'
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 1000, "expected_value_after_useful_life": 1000,
@ -653,10 +653,10 @@ class TestAsset(unittest.TestCase):
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [ expected_schedules = [
["2030-12-31", 1106.85, 1106.85], ["2030-12-31", 942.47, 942.47],
["2031-12-31", 3446.58, 4553.43], ["2031-12-31", 3528.77, 4471.24],
["2032-12-31", 1723.29, 6276.72], ["2032-12-31", 1764.38, 6235.62],
["2033-06-12", 723.28, 7000.00] ["2033-07-12", 764.38, 7000.00]
] ]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]

View File

@ -15,6 +15,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
class TestAssetMovement(unittest.TestCase): class TestAssetMovement(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC")
create_asset_data() create_asset_data()
make_location() make_location()
@ -45,12 +46,12 @@ class TestAssetMovement(unittest.TestCase):
'location_name': 'Test Location 2' 'location_name': 'Test Location 2'
}).insert() }).insert()
movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name) reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, create_asset_movement(purpose = 'Transfer', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name) reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
@ -59,18 +60,18 @@ class TestAssetMovement(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
employee = make_employee("testassetmovemp@example.com", company="_Test Company") employee = make_employee("testassetmovemp@example.com", company="_Test Company")
movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, create_asset_movement(purpose = 'Issue', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name) reference_doctype = 'Purchase Receipt', reference_name = pr.name)
# after issuing asset should belong to an employee not at a location # after issuing asset should belong to an employee not at a location
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee) self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
def test_last_movement_cancellation(self): def test_last_movement_cancellation(self):
pr = make_purchase_receipt(item_code="Macbook Pro", pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.0, location="Test Location") qty=1, rate=100000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
@ -85,17 +86,17 @@ class TestAssetMovement(unittest.TestCase):
}) })
if asset.docstatus == 0: if asset.docstatus == 0:
asset.submit() asset.submit()
if not frappe.db.exists("Location", "Test Location 2"): if not frappe.db.exists("Location", "Test Location 2"):
frappe.get_doc({ frappe.get_doc({
'doctype': 'Location', 'doctype': 'Location',
'location_name': 'Test Location 2' 'location_name': 'Test Location 2'
}).insert() }).insert()
movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name }) movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name })
self.assertRaises(frappe.ValidationError, movement.cancel) self.assertRaises(frappe.ValidationError, movement.cancel)
movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name) reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")

View File

@ -1288,6 +1288,27 @@ def validate_taxes_and_charges(tax):
tax.rate = None tax.rate = None
def validate_account_head(tax, doc):
company = frappe.get_cached_value('Account',
tax.account_head, 'company')
if company != doc.company:
frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
.format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account'))
def validate_cost_center(tax, doc):
if not tax.cost_center:
return
company = frappe.get_cached_value('Cost Center',
tax.cost_center, 'company')
if company != doc.company:
frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}')
.format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center'))
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, row_range)) throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
@ -1507,7 +1528,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
if child_item.get("item_tax_template"): if child_item.get("item_tax_template"):
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True) child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
def add_taxes_from_tax_template(child_item, parent_doc): def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template") add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template: if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
@ -1530,7 +1551,8 @@ def add_taxes_from_tax_template(child_item, parent_doc):
"category" : "Total", "category" : "Total",
"add_deduct_tax" : "Add" "add_deduct_tax" : "Add"
}) })
tax_row.db_insert() if db_insert:
tax_row.db_insert()
def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item): def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
""" """

View File

@ -27,6 +27,7 @@ class StockController(AccountsController):
if not self.get('is_return'): if not self.get('is_return'):
self.validate_inspection() self.validate_inspection()
self.validate_serialized_batch() self.validate_serialized_batch()
self.clean_serial_nos()
self.validate_customer_provided_item() self.validate_customer_provided_item()
self.set_rate_of_stock_uom() self.set_rate_of_stock_uom()
self.validate_internal_transfer() self.validate_internal_transfer()
@ -72,6 +73,12 @@ class StockController(AccountsController):
frappe.throw(_("Row #{0}: The batch {1} has already expired.") frappe.throw(_("Row #{0}: The batch {1} has already expired.")
.format(d.idx, get_link_to_form("Batch", d.get("batch_no")))) .format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
def clean_serial_nos(self):
for row in self.get("items"):
if hasattr(row, "serial_no") and row.serial_no:
# replace commas by linefeed and remove all spaces in string
row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "")
def get_gl_entries(self, warehouse_account=None, default_expense_account=None, def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None): default_cost_center=None):

View File

@ -679,17 +679,13 @@ class calculate_taxes_and_totals(object):
default_mode_of_payment = frappe.db.get_value('POS Payment Method', default_mode_of_payment = frappe.db.get_value('POS Payment Method',
{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1) {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
self.doc.payments = []
if default_mode_of_payment: if default_mode_of_payment:
self.doc.payments = []
self.doc.append('payments', { self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment, 'mode_of_payment': default_mode_of_payment.mode_of_payment,
'amount': total_amount_to_pay, 'amount': total_amount_to_pay,
'default': 1 'default': 1
}) })
else:
self.doc.is_pos = 0
self.doc.pos_profile = ''
self.calculate_paid_amount() self.calculate_paid_amount()

View File

@ -9,7 +9,7 @@ import datetime
def create_test_lead(): def create_test_lead():
test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'}) test_lead = frappe.db.exists({'doctype': 'Lead', 'email_id':'test@example.com'})
if test_lead: if test_lead:
return frappe.get_doc('Lead', test_lead[0][0]) return frappe.get_doc('Lead', test_lead[0][0])
test_lead = frappe.get_doc({ test_lead = frappe.get_doc({

View File

@ -12,7 +12,8 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
'Opportunity': this.make_opportunity 'Opportunity': this.make_opportunity
}; };
this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead); // For avoiding integration issues.
this.frm.set_df_property('first_name', 'reqd', true);
} }
onload () { onload () {
@ -42,6 +43,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
if (!this.frm.is_new()) { if (!this.frm.is_new()) {
frappe.contacts.render_address_and_contact(this.frm); frappe.contacts.render_address_and_contact(this.frm);
cur_frm.trigger('render_contact_day_html');
} else { } else {
frappe.contacts.clear_address_and_contact(this.frm); frappe.contacts.clear_address_and_contact(this.frm);
} }
@ -68,13 +70,8 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
}) })
} }
organization_lead () {
this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
this.frm.toggle_reqd("company_name", this.frm.doc.organization_lead);
}
company_name () { company_name () {
if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) { if (!this.frm.doc.lead_name) {
this.frm.set_value("lead_name", this.frm.doc.company_name); this.frm.set_value("lead_name", this.frm.doc.company_name);
} }
} }
@ -86,6 +83,19 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat)); this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat));
} }
} }
render_contact_day_html() {
if (cur_frm.doc.contact_date) {
let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date);
let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today());
let color = diff_days > 0 ? "orange" : "green";
let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date");
let html = `<div class="col-xs-12">
<span class="indicator whitespace-nowrap ${color}"><span> ${message} : ${frappe.datetime.global_date_format(contact_date)}</span></span>
</div>` ;
cur_frm.dashboard.set_headline_alert(html);
}
}
}; };
extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm })); extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));

View File

@ -9,71 +9,70 @@
"email_append_to": 1, "email_append_to": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"organization_lead",
"lead_details", "lead_details",
"naming_series", "naming_series",
"lead_name",
"company_name",
"email_id",
"col_break123",
"lead_owner",
"status",
"salutation", "salutation",
"first_name",
"middle_name",
"last_name",
"lead_name",
"col_break123",
"status",
"company_name",
"designation", "designation",
"gender", "gender",
"source", "contact_details_section",
"customer", "email_id",
"campaign_name", "mobile_no",
"image", "whatsapp_no",
"section_break_12", "column_break_16",
"contact_by", "phone",
"column_break_14", "phone_ext",
"contact_date", "additional_information_section",
"ends_on", "no_of_employees",
"notes_section", "industry",
"notes", "market_segment",
"address_info", "column_break_22",
"fax",
"website",
"type",
"request_type",
"address_section",
"address_html", "address_html",
"address_type",
"address_title",
"address_line1",
"address_line2",
"city", "city",
"pincode",
"county", "county",
"column_break2", "column_break2",
"contact_html", "contact_html",
"state", "state",
"country", "country",
"pincode", "section_break_12",
"contact_section", "lead_owner",
"phone", "ends_on",
"mobile_no", "column_break_14",
"fax", "contact_by",
"website", "contact_date",
"more_info", "lead_source_details_section",
"type",
"market_segment",
"industry",
"request_type",
"column_break3",
"company", "company",
"territory", "territory",
"language", "language",
"column_break_50",
"source",
"campaign_name",
"unsubscribed", "unsubscribed",
"blog_subscriber", "blog_subscriber",
"notes_section",
"notes",
"other_information_section",
"customer",
"image",
"title" "title"
], ],
"fields": [ "fields": [
{
"default": "0",
"fieldname": "organization_lead",
"fieldtype": "Check",
"label": "Lead is an Organization",
"set_only_once": 1
},
{ {
"fieldname": "lead_details", "fieldname": "lead_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Lead Details",
"options": "fa fa-user" "options": "fa fa-user"
}, },
{ {
@ -90,16 +89,19 @@
"fieldname": "lead_name", "fieldname": "lead_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_global_search": 1, "in_global_search": 1,
"label": "Person Name", "label": "Full Name",
"oldfieldname": "lead_name", "oldfieldname": "lead_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"read_only": 1,
"search_index": 1 "search_index": 1
}, },
{ {
"fieldname": "company_name", "fieldname": "company_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1,
"label": "Organization Name", "label": "Organization Name",
"mandatory_depends_on": "eval: !(doc.first_name)",
"oldfieldname": "company_name", "oldfieldname": "company_name",
"oldfieldtype": "Data" "oldfieldtype": "Data"
}, },
@ -121,7 +123,6 @@
"default": "__user", "default": "__user",
"fieldname": "lead_owner", "fieldname": "lead_owner",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Lead Owner", "label": "Lead Owner",
"oldfieldname": "lead_owner", "oldfieldname": "lead_owner",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@ -143,7 +144,6 @@
"search_index": 1 "search_index": 1
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "salutation", "fieldname": "salutation",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Salutation", "label": "Salutation",
@ -241,46 +241,22 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval: doc.__islocal",
"description": "Home, Work, etc.",
"fieldname": "address_title",
"fieldtype": "Data",
"label": "Address Title"
},
{
"depends_on": "eval: doc.__islocal",
"fieldname": "address_line1",
"fieldtype": "Data",
"label": "Address Line 1",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
},
{
"depends_on": "eval: doc.__islocal",
"fieldname": "address_line2",
"fieldtype": "Data",
"label": "Address Line 2"
},
{
"depends_on": "eval: doc.__islocal",
"fieldname": "city", "fieldname": "city",
"fieldtype": "Data", "fieldtype": "Data",
"label": "City/Town", "label": "City/Town",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type" "mandatory_depends_on": "eval: doc.address_title && doc.address_type"
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "county", "fieldname": "county",
"fieldtype": "Data", "fieldtype": "Data",
"label": "County" "label": "County"
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "state", "fieldname": "state",
"fieldtype": "Data", "fieldtype": "Data",
"label": "State" "label": "State"
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "country", "fieldname": "country",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Country", "label": "Country",
@ -288,7 +264,6 @@
"options": "Country" "options": "Country"
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "pincode", "fieldname": "pincode",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Postal Code" "label": "Postal Code"
@ -304,7 +279,6 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "phone", "fieldname": "phone",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Phone", "label": "Phone",
@ -313,7 +287,6 @@
"options": "Phone" "options": "Phone"
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "mobile_no", "fieldname": "mobile_no",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Mobile No.", "label": "Mobile No.",
@ -322,21 +295,12 @@
"options": "Phone" "options": "Phone"
}, },
{ {
"depends_on": "eval: doc.__islocal",
"fieldname": "fax", "fieldname": "fax",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Fax", "label": "Fax",
"oldfieldname": "fax", "oldfieldname": "fax",
"oldfieldtype": "Data" "oldfieldtype": "Data"
}, },
{
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text"
},
{ {
"fieldname": "type", "fieldname": "type",
"fieldtype": "Select", "fieldtype": "Select",
@ -369,12 +333,6 @@
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther" "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther"
}, },
{
"fieldname": "column_break3",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"width": "50%"
},
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
@ -389,11 +347,14 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Website", "label": "Website",
"oldfieldname": "website", "oldfieldname": "website",
"oldfieldtype": "Data" "oldfieldtype": "Data",
"options": "URL"
}, },
{ {
"fieldname": "territory", "fieldname": "territory",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Territory", "label": "Territory",
"oldfieldname": "territory", "oldfieldname": "territory",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@ -422,45 +383,95 @@
{ {
"fieldname": "designation", "fieldname": "designation",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Designation", "label": "Designation",
"options": "Designation" "options": "Designation"
}, },
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.__islocal",
"fieldname": "address_info",
"fieldtype": "Section Break",
"label": "Address & Contact",
"oldfieldtype": "Column Break",
"options": "fa fa-map-marker"
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.__islocal",
"fieldname": "contact_section",
"fieldtype": "Section Break",
"label": "Contact"
},
{
"default": "Billing",
"depends_on": "eval: doc.__islocal",
"fieldname": "address_type",
"fieldtype": "Select",
"label": "Address Type",
"options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nCurrent\nPermanent\nOther"
},
{ {
"fieldname": "language", "fieldname": "language",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Print Language", "label": "Print Language",
"options": "Language" "options": "Language"
},
{
"fieldname": "first_name",
"fieldtype": "Data",
"label": "First Name",
"mandatory_depends_on": "eval: !(doc.company_name)"
},
{
"fieldname": "middle_name",
"fieldtype": "Data",
"label": "Middle Name"
},
{
"fieldname": "last_name",
"fieldtype": "Data",
"label": "Last Name"
},
{
"collapsible": 1,
"fieldname": "additional_information_section",
"fieldtype": "Section Break",
"label": "Additional Information"
},
{
"fieldname": "no_of_employees",
"fieldtype": "Int",
"label": "No. of Employees"
},
{
"fieldname": "column_break_22",
"fieldtype": "Column Break"
},
{
"fieldname": "whatsapp_no",
"fieldtype": "Data",
"label": "WhatsApp No.",
"options": "Phone"
},
{
"collapsible": 1,
"depends_on": "eval: !doc.__islocal",
"fieldname": "address_section",
"fieldtype": "Section Break",
"label": "Address"
},
{
"fieldname": "lead_source_details_section",
"fieldtype": "Section Break",
"label": "Lead Source Details"
},
{
"fieldname": "column_break_50",
"fieldtype": "Column Break"
},
{
"fieldname": "other_information_section",
"fieldtype": "Section Break",
"label": "Other Information"
},
{
"fieldname": "contact_details_section",
"fieldtype": "Section Break",
"label": "Contact Details"
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"fieldname": "phone_ext",
"fieldtype": "Data",
"label": "Phone Ext."
} }
], ],
"icon": "fa fa-user", "icon": "fa fa-user",
"idx": 5, "idx": 5,
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2021-01-06 19:39:58.748978", "modified": "2021-08-04 00:24:57.208590",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Lead", "name": "Lead",

View File

@ -21,26 +21,24 @@ class Lead(SellingController):
self.get("__onload").is_customer = customer self.get("__onload").is_customer = customer
load_address_and_contact(self) load_address_and_contact(self)
def before_insert(self):
if self.address_title and self.address_type:
self.address_doc = self.create_address()
self.contact_doc = self.create_contact()
def after_insert(self):
self.update_links()
def validate(self): def validate(self):
self.set_full_name()
self.set_lead_name() self.set_lead_name()
self.set_title() self.set_title()
self.set_status()
self.check_email_id_is_unique()
self.validate_email_id()
self.validate_contact_date()
self._prev = frappe._dict({ self._prev = frappe._dict({
"contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None, "contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None,
"ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None, "ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None,
"contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None, "contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None,
}) })
def set_full_name(self):
self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
self.set_status() def validate_email_id(self):
self.check_email_id_is_unique()
if self.email_id: if self.email_id:
if not self.flags.ignore_email_validation: if not self.flags.ignore_email_validation:
validate_email_address(self.email_id, throw=True) validate_email_address(self.email_id, throw=True)
@ -54,6 +52,7 @@ class Lead(SellingController):
if self.is_new() or not self.image: if self.is_new() or not self.image:
self.image = has_gravatar(self.email_id) self.image = has_gravatar(self.email_id)
def validate_contact_date(self):
if self.contact_date and getdate(self.contact_date) < getdate(nowdate()): if self.contact_date and getdate(self.contact_date) < getdate(nowdate()):
frappe.throw(_("Next Contact Date cannot be in the past")) frappe.throw(_("Next Contact Date cannot be in the past"))
@ -64,6 +63,22 @@ class Lead(SellingController):
def on_update(self): def on_update(self):
self.add_calendar_event() self.add_calendar_event()
def before_insert(self):
self.contact_doc = self.create_contact()
def after_insert(self):
self.update_links()
def update_links(self):
# update contact links
if self.contact_doc:
self.contact_doc.append("links", {
"link_doctype": "Lead",
"link_name": self.name,
"link_title": self.lead_name
})
self.contact_doc.save()
def add_calendar_event(self, opts=None, force=False): def add_calendar_event(self, opts=None, force=False):
super(Lead, self).add_calendar_event({ super(Lead, self).add_calendar_event({
"owner": self.lead_owner, "owner": self.lead_owner,
@ -86,8 +101,26 @@ class Lead(SellingController):
def on_trash(self): def on_trash(self):
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name) frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
self.unlink_dynamic_links()
self.delete_events() self.delete_events()
def unlink_dynamic_links(self):
links = frappe.get_all('Dynamic Link', filters={'link_doctype': self.doctype, 'link_name': self.name}, fields=['parent', 'parenttype'])
for link in links:
linked_doc = frappe.get_doc(link['parenttype'], link['parent'])
if len(linked_doc.get('links')) == 1:
linked_doc.delete(ignore_permissions=True)
else:
to_remove = None
for d in linked_doc.get('links'):
if d.link_doctype == self.doctype and d.link_name == self.name:
to_remove = d
if to_remove:
linked_doc.remove(to_remove)
linked_doc.save(ignore_permissions=True)
def has_customer(self): def has_customer(self):
return frappe.db.get_value("Customer", {"lead_name": self.name}) return frappe.db.get_value("Customer", {"lead_name": self.name})
@ -99,7 +132,6 @@ class Lead(SellingController):
"party_name": self.name, "party_name": self.name,
"docstatus": 1, "docstatus": 1,
"status": ["!=", "Lost"] "status": ["!=", "Lost"]
}) })
def has_lost_quotation(self): def has_lost_quotation(self):
@ -120,40 +152,17 @@ class Lead(SellingController):
self.lead_name = self.email_id.split("@")[0] self.lead_name = self.email_id.split("@")[0]
def set_title(self): def set_title(self):
if self.organization_lead: self.title = self.company_name or self.lead_name
self.title = self.company_name
else:
self.title = self.lead_name
def create_address(self):
address_fields = ["address_type", "address_title", "address_line1", "address_line2",
"city", "county", "state", "country", "pincode"]
info_fields = ["email_id", "phone", "fax"]
# do not create an address if no fields are available,
# skipping country since the system auto-sets it from system defaults
address = frappe.new_doc("Address")
address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
address.update({info_field: self.get(info_field) for info_field in info_fields})
address.insert()
return address
def create_contact(self): def create_contact(self):
if not self.lead_name: if not self.lead_name:
self.set_full_name()
self.set_lead_name() self.set_lead_name()
names = self.lead_name.strip().split(" ")
if len(names) > 1:
first_name, last_name = names[0], " ".join(names[1:])
else:
first_name, last_name = self.lead_name, None
contact = frappe.new_doc("Contact") contact = frappe.new_doc("Contact")
contact.update({ contact.update({
"first_name": first_name, "first_name": self.first_name or self.lead_name,
"last_name": last_name, "last_name": self.last_name,
"salutation": self.salutation, "salutation": self.salutation,
"gender": self.gender, "gender": self.gender,
"designation": self.designation, "designation": self.designation,
@ -181,25 +190,6 @@ class Lead(SellingController):
return contact return contact
def update_links(self):
# update address links
if hasattr(self, 'address_doc'):
self.address_doc.append("links", {
"link_doctype": "Lead",
"link_name": self.name,
"link_title": self.lead_name
})
self.address_doc.save()
# update contact links
if self.contact_doc:
self.contact_doc.append("links", {
"link_doctype": "Lead",
"link_name": self.name,
"link_title": self.lead_name
})
self.contact_doc.save()
@frappe.whitelist() @frappe.whitelist()
def make_customer(source_name, target_doc=None): def make_customer(source_name, target_doc=None):
return _make_customer(source_name, target_doc) return _make_customer(source_name, target_doc)

View File

@ -4,6 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import random_string
import unittest import unittest
test_records = frappe.get_test_records('Lead') test_records = frappe.get_test_records('Lead')
@ -32,3 +33,53 @@ class TestLead(unittest.TestCase):
customer.company = "_Test Company" customer.company = "_Test Company"
customer.customer_group = "_Test Customer Group" customer.customer_group = "_Test Customer Group"
customer.insert() customer.insert()
def test_create_lead_and_unlinking_dynamic_links(self):
lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum", email_id="lorem_ipsum@example.com")
lead_doc_1 = make_lead()
frappe.get_doc({
"doctype": "Address",
"address_type": "Billing",
"city": "Mumbai",
"address_line1": "Vidya Vihar West",
"country": "India",
"links": [{
"link_doctype": "Lead",
"link_name": lead_doc.name
}]
}).insert()
address_1 = frappe.get_doc({
"doctype": "Address",
"address_type": "Billing",
"address_line1": "Baner",
"city": "Pune",
"country": "India",
"links": [
{
"link_doctype": "Lead",
"link_name": lead_doc.name
},
{
"link_doctype": "Lead",
"link_name": lead_doc_1.name
}
]
}).insert()
lead_doc.delete()
address_1.reload()
self.assertEqual(frappe.db.exists("Lead",lead_doc.name), None)
self.assertEqual(len(address_1.get('links')), 1)
def make_lead(**args):
args = frappe._dict(args)
lead_doc = frappe.get_doc({
"doctype": "Lead",
"first_name": args.first_name or "_Test",
"last_name": args.last_name or "Lead",
"email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)),
}).insert()
return lead_doc

View File

@ -27,7 +27,6 @@
{ {
"doctype": "Lead", "doctype": "Lead",
"email_id": "test_lead4@example.com", "email_id": "test_lead4@example.com",
"organization_lead": 1,
"lead_name": "_Test Lead 4", "lead_name": "_Test Lead 4",
"company_name": "_Test Lead 4", "company_name": "_Test Lead 4",
"status": "Open" "status": "Open"

View File

@ -9,7 +9,6 @@ QUnit.test("test: lead", function (assert) {
() => frappe.set_route("List", "Lead"), () => frappe.set_route("List", "Lead"),
() => frappe.new_doc("Lead"), () => frappe.new_doc("Lead"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => cur_frm.set_value("organization_lead", "1"),
() => cur_frm.set_value("company_name", lead_name), () => cur_frm.set_value("company_name", lead_name),
() => cur_frm.save(), () => cur_frm.save(),
() => frappe.timeout(1), () => frappe.timeout(1),

View File

@ -53,6 +53,13 @@ frappe.ui.form.on("Opportunity", {
frm.get_field("items").grid.set_multiple_add("item_code", "qty"); frm.get_field("items").grid.set_multiple_add("item_code", "qty");
}, },
status:function(frm){
if (frm.doc.status == "Lost"){
frm.trigger('set_as_lost_dialog');
}
},
customer_address: function(frm, cdt, cdn) { customer_address: function(frm, cdt, cdn) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false); erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false);
}, },
@ -91,11 +98,6 @@ frappe.ui.form.on("Opportunity", {
frm.add_custom_button(__('Quotation'), frm.add_custom_button(__('Quotation'),
cur_frm.cscript.create_quotation, __('Create')); cur_frm.cscript.create_quotation, __('Create'));
if(doc.status!=="Quotation") {
frm.add_custom_button(__('Lost'), () => {
frm.trigger('set_as_lost_dialog');
});
}
} }
if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) { if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {

View File

@ -15,7 +15,11 @@ class TestAttendanceRequest(unittest.TestCase):
for doctype in ["Attendance Request", "Attendance"]: for doctype in ["Attendance Request", "Attendance"]:
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
def tearDown(self):
frappe.db.rollback()
def test_on_duty_attendance_request(self): def test_on_duty_attendance_request(self):
"Test creation/updation of Attendace from Attendance Request, on duty."
today = nowdate() today = nowdate()
employee = get_employee() employee = get_employee()
attendance_request = frappe.new_doc("Attendance Request") attendance_request = frappe.new_doc("Attendance Request")
@ -26,17 +30,36 @@ class TestAttendanceRequest(unittest.TestCase):
attendance_request.company = "_Test Company" attendance_request.company = "_Test Company"
attendance_request.insert() attendance_request.insert()
attendance_request.submit() attendance_request.submit()
attendance = frappe.get_doc('Attendance', {
'employee': employee.name, attendance = frappe.db.get_value(
'attendance_date': date(date.today().year, 1, 1), "Attendance",
'docstatus': 1 filters={
}) "attendance_request": attendance_request.name,
self.assertEqual(attendance.status, 'Present') "attendance_date": date(date.today().year, 1, 1)
},
fieldname=["status", "docstatus"],
as_dict=True
)
self.assertEqual(attendance.status, "Present")
self.assertEqual(attendance.docstatus, 1)
# cancelling attendance request cancels linked attendances
attendance_request.cancel() attendance_request.cancel()
attendance.reload()
self.assertEqual(attendance.docstatus, 2) # cancellation alters docname
# fetch attendance value again to avoid stale docname
attendance_docstatus = frappe.db.get_value(
"Attendance",
filters={
"attendance_request": attendance_request.name,
"attendance_date": date(date.today().year, 1, 1)
},
fieldname="docstatus"
)
self.assertEqual(attendance_docstatus, 2)
def test_work_from_home_attendance_request(self): def test_work_from_home_attendance_request(self):
"Test creation/updation of Attendace from Attendance Request, work from home."
today = nowdate() today = nowdate()
employee = get_employee() employee = get_employee()
attendance_request = frappe.new_doc("Attendance Request") attendance_request = frappe.new_doc("Attendance Request")
@ -47,15 +70,30 @@ class TestAttendanceRequest(unittest.TestCase):
attendance_request.company = "_Test Company" attendance_request.company = "_Test Company"
attendance_request.insert() attendance_request.insert()
attendance_request.submit() attendance_request.submit()
attendance = frappe.get_doc('Attendance', {
'employee': employee.name, attendance_status = frappe.db.get_value(
'attendance_date': date(date.today().year, 1, 1), "Attendance",
'docstatus': 1 filters={
}) "attendance_request": attendance_request.name,
self.assertEqual(attendance.status, 'Work From Home') "attendance_date": date(date.today().year, 1, 1)
},
fieldname="status"
)
self.assertEqual(attendance_status, 'Work From Home')
attendance_request.cancel() attendance_request.cancel()
attendance.reload()
self.assertEqual(attendance.docstatus, 2) # cancellation alters docname
# fetch attendance value again to avoid stale docname
attendance_docstatus = frappe.db.get_value(
"Attendance",
filters={
"attendance_request": attendance_request.name,
"attendance_date": date(date.today().year, 1, 1)
},
fieldname="docstatus"
)
self.assertEqual(attendance_docstatus, 2)
def get_employee(): def get_employee():
return frappe.get_doc("Employee", "_T-Employee-00001") return frappe.get_doc("Employee", "_T-Employee-00001")

View File

@ -15,24 +15,35 @@ class TestShiftRequest(unittest.TestCase):
for doctype in ["Shift Request", "Shift Assignment"]: for doctype in ["Shift Request", "Shift Assignment"]:
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
def tearDown(self):
frappe.db.rollback()
def test_make_shift_request(self): def test_make_shift_request(self):
"Test creation/updation of Shift Assignment from Shift Request."
department = frappe.get_value("Employee", "_T-Employee-00001", 'department') department = frappe.get_value("Employee", "_T-Employee-00001", 'department')
set_shift_approver(department) set_shift_approver(department)
approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0] approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
shift_request = make_shift_request(approver) shift_request = make_shift_request(approver)
shift_assignments = frappe.db.sql(''' # Only one shift assignment is created against a shift request
SELECT shift_request, employee shift_assignment = frappe.db.get_value(
FROM `tabShift Assignment` "Shift Assignment",
WHERE shift_request = '{0}' filters={"shift_request": shift_request.name},
'''.format(shift_request.name), as_dict=1) fieldname=["employee", "docstatus"],
for d in shift_assignments: as_dict=True
employee = d.get('employee') )
self.assertEqual(shift_request.employee, employee) self.assertEqual(shift_request.employee, shift_assignment.employee)
shift_request.cancel() self.assertEqual(shift_assignment.docstatus, 1)
shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')})
self.assertEqual(shift_assignment_doc.docstatus, 2) shift_request.cancel()
shift_assignment_docstatus = frappe.db.get_value(
"Shift Assignment",
filters={"shift_request": shift_request.name},
fieldname="docstatus"
)
self.assertEqual(shift_assignment_docstatus, 2)
def test_shift_request_approver_perms(self): def test_shift_request_approver_perms(self):
employee = frappe.get_doc("Employee", "_T-Employee-00001") employee = frappe.get_doc("Employee", "_T-Employee-00001")

View File

@ -717,9 +717,8 @@ def get_bom_item_rate(args, bom_doc):
"ignore_conversion_rate": True "ignore_conversion_rate": True
}) })
item_doc = frappe.get_cached_doc("Item", args.get("item_code")) item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
out = frappe._dict() price_list_data = get_price_list_rate(bom_args, item_doc)
get_price_list_rate(bom_args, item_doc, out) rate = price_list_data.price_list_rate
rate = out.price_list_rate
return rate return rate

View File

@ -608,6 +608,11 @@ def make_stock_entry(source_name, target_doc=None):
target.set_missing_values() target.set_missing_values()
target.set_stock_entry_type() target.set_stock_entry_type()
wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item")
for item in target.items:
item.allow_alternative_item = int(wo_allows_alternate_item and
frappe.get_cached_value("Item", item.item_code, "allow_alternative_item"))
doclist = get_mapped_doc("Job Card", source_name, { doclist = get_mapped_doc("Job Card", source_name, {
"Job Card": { "Job Card": {
"doctype": "Stock Entry", "doctype": "Stock Entry",
@ -698,4 +703,4 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist return doclist

View File

@ -294,7 +294,9 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_subscription_status_in_memberships
erpnext.patches.v13_0.update_amt_in_work_order_required_items erpnext.patches.v13_0.update_amt_in_work_order_required_items
erpnext.patches.v12_0.show_einvoice_irn_cancelled_field
erpnext.patches.v13_0.delete_orphaned_tables
erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_export_type_for_gst
erpnext.patches.v13_0.update_tds_check_field #3 erpnext.patches.v13_0.update_tds_check_field #3
erpnext.patches.v13_0.add_custom_field_for_south_africa erpnext.patches.v13_0.add_custom_field_for_south_africa #2
erpnext.patches.v13_0.shopify_deprecation_warning erpnext.patches.v13_0.shopify_deprecation_warning

View File

@ -0,0 +1,12 @@
from __future__ import unicode_literals
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'})
if irn_cancelled_field:
frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn')
frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0)

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from erpnext.regional.south_africa.setup import make_custom_fields from erpnext.regional.south_africa.setup import make_custom_fields, add_permissions
def execute(): def execute():
company = frappe.get_all('Company', filters = {'country': 'South Africa'}) company = frappe.get_all('Company', filters = {'country': 'South Africa'})
@ -11,3 +11,4 @@ def execute():
return return
make_custom_fields() make_custom_fields()
add_permissions()

View File

@ -0,0 +1,69 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate
def execute():
frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record')
if has_deleted_company_transactions():
child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected()
for doctype in child_doctypes:
docs = frappe.get_all(doctype, fields=['name', 'parent', 'parenttype', 'creation'])
for doc in docs:
if not frappe.db.exists(doc['parenttype'], doc['parent']):
frappe.db.delete(doctype, {'name': doc['name']})
elif check_for_new_doc_with_same_name_as_deleted_parent(doc):
frappe.db.delete(doctype, {'name': doc['name']})
def has_deleted_company_transactions():
return frappe.get_all('Transaction Deletion Record')
def get_child_doctypes_whose_parent_doctypes_were_affected():
parent_doctypes = get_affected_doctypes()
child_doctypes = frappe.get_all(
'DocField',
filters={
'fieldtype': 'Table',
'parent':['in', parent_doctypes]
}, pluck='options')
return child_doctypes
def get_affected_doctypes():
affected_doctypes = []
tdr_docs = frappe.get_all('Transaction Deletion Record', pluck="name")
for tdr in tdr_docs:
tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr)
for doctype in tdr_doc.doctypes:
if is_not_child_table(doctype.doctype_name):
affected_doctypes.append(doctype.doctype_name)
affected_doctypes = remove_duplicate_items(affected_doctypes)
return affected_doctypes
def is_not_child_table(doctype):
return not bool(frappe.get_value('DocType', doctype, 'istable'))
def remove_duplicate_items(affected_doctypes):
return list(set(affected_doctypes))
def check_for_new_doc_with_same_name_as_deleted_parent(doc):
"""
Compares creation times of parent and child docs.
Since Transaction Deletion Record resets the naming series after deletion,
it allows the creation of new docs with the same names as the deleted ones.
"""
parent_creation_time = frappe.db.get_value(doc['parenttype'], doc['parent'], 'creation')
child_creation_time = doc['creation']
return getdate(parent_creation_time) > getdate(child_creation_time)

View File

@ -647,10 +647,13 @@ class SalarySlip(TransactionBase):
continue continue
if ( if (
(not d.additional_salary (
and (not additional_salary or additional_salary.overwrite)) not d.additional_salary
or (additional_salary and (not additional_salary or additional_salary.overwrite)
and additional_salary.name == d.additional_salary) ) or (
additional_salary
and additional_salary.name == d.additional_salary
)
): ):
component_row = d component_row = d
break break
@ -679,8 +682,12 @@ class SalarySlip(TransactionBase):
if additional_salary: if additional_salary:
component_row.is_recurring_additional_salary = is_recurring component_row.is_recurring_additional_salary = is_recurring
component_row.default_amount = 0 if additional_salary.overwrite:
component_row.additional_amount = amount component_row.additional_amount = flt(flt(amount) - flt(component_row.get("default_amount", 0)),
component_row.precision("additional_amount"))
else:
component_row.default_amount = 0
component_row.additional_amount = amount
component_row.additional_salary = additional_salary.name component_row.additional_salary = additional_salary.name
component_row.deduct_full_tax_on_selected_payroll_date = \ component_row.deduct_full_tax_on_selected_payroll_date = \
additional_salary.deduct_full_tax_on_selected_payroll_date additional_salary.deduct_full_tax_on_selected_payroll_date

View File

@ -31,6 +31,14 @@ frappe.ui.form.on(cur_frm.doctype, {
} }
} }
}); });
frm.set_query("cost_center", "taxes", function(doc) {
return {
filters: {
"company": doc.company,
"is_group": 0
}
};
});
} }
}, },
validate: function(frm) { validate: function(frm) {

View File

@ -751,8 +751,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.doc.payments.find(pay => { this.frm.doc.payments.find(pay => {
if (pay.default) { if (pay.default) {
pay.amount = total_amount_to_pay; pay.amount = total_amount_to_pay;
} else {
pay.amount = 0.0
} }
}); });
this.frm.refresh_fields(); this.frm.refresh_fields();

View File

@ -752,7 +752,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.frm.trigger("item_code", cdt, cdn); this.frm.trigger("item_code", cdt, cdn);
} }
else { else {
// Replacing all occurences of comma with carriage return // Replace all occurences of comma with line feed
item.serial_no = item.serial_no.replace(/,/g, '\n'); item.serial_no = item.serial_no.replace(/,/g, '\n');
item.conversion_factor = item.conversion_factor || 1; item.conversion_factor = item.conversion_factor || 1;
refresh_field("serial_no", item.name, item.parentfield); refresh_field("serial_no", item.name, item.parentfield);

View File

@ -0,0 +1,5 @@
{% if address_line1 %}{{ address_line1 }}{% endif -%}
{% if address_line2 %}<br>{{ address_line2 }}{% endif -%}
{% if pincode %}<br>{{ pincode }}{% endif -%}
{% if city %} {{ city }}{% endif -%}
{% if country %}<br>{{ country }}{% endif -%}

View File

@ -316,10 +316,6 @@ def get_payment_details(invoice):
)) ))
def get_return_doc_reference(invoice): def get_return_doc_reference(invoice):
if not invoice.return_against:
frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
.format(frappe.bold('Return Against')), title=_('Missing Field'))
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
return frappe._dict(dict( return frappe._dict(dict(
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
@ -438,7 +434,7 @@ def make_einvoice(invoice):
if invoice.is_pos and invoice.base_paid_amount: if invoice.is_pos and invoice.base_paid_amount:
payment_details = get_payment_details(invoice) payment_details = get_payment_details(invoice)
if invoice.is_return: if invoice.is_return and invoice.return_against:
prev_doc_details = get_return_doc_reference(invoice) prev_doc_details = get_return_doc_reference(invoice)
if invoice.transporter and not invoice.is_return: if invoice.transporter and not invoice.is_return:
@ -932,7 +928,7 @@ class GSPConnector():
def set_einvoice_data(self, res): def set_einvoice_data(self, res):
enc_signed_invoice = res.get('SignedInvoice') enc_signed_invoice = res.get('SignedInvoice')
dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data'] dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data']
self.invoice.irn = res.get('Irn') self.invoice.irn = res.get('Irn')
self.invoice.ewaybill = res.get('EwbNo') self.invoice.ewaybill = res.get('EwbNo')
@ -1130,4 +1126,4 @@ def check_scheduler_status():
def job_already_enqueued(job_name): def job_already_enqueued(job_name):
enqueued_jobs = [d.get("job_name") for d in get_info()] enqueued_jobs = [d.get("job_name") for d in get_info()]
if job_name in enqueued_jobs: if job_name in enqueued_jobs:
return True return True

View File

@ -457,7 +457,7 @@ def make_custom_fields(update=True):
depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'),
dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'),
@ -985,4 +985,4 @@ def create_gratuity_rule():
def update_accounts_settings_for_taxes(): def update_accounts_settings_for_taxes():
if frappe.db.count('Company') == 1: if frappe.db.count('Company') == 1:
frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0)

View File

@ -851,7 +851,7 @@ def get_depreciation_amount(asset, depreciable_value, row):
# if its the first depreciation # if its the first depreciation
if depreciable_value == asset.gross_purchase_amount: if depreciable_value == asset.gross_purchase_amount:
# as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
diff = date_diff(asset.available_for_use_date, row.depreciation_start_date) diff = date_diff(row.depreciation_start_date, asset.available_for_use_date)
if diff <= 180: if diff <= 180:
rate_of_depreciation = rate_of_depreciation / 2 rate_of_depreciation = rate_of_depreciation / 2
frappe.msgprint( frappe.msgprint(

View File

@ -18,15 +18,5 @@
"ref_doctype": "GL Entry", "ref_doctype": "GL Entry",
"report_name": "VAT Audit Report", "report_name": "VAT Audit Report",
"report_type": "Script Report", "report_type": "Script Report",
"roles": [ "roles": []
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
} }

View File

@ -189,6 +189,8 @@ class VATAuditReport(object):
row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy") row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy")
row["voucher_type"] = doctype row["voucher_type"] = doctype
row["voucher_no"] = inv row["voucher_no"] = inv
row["party_type"] = "Customer" if doctype == "Sales Invoice" else "Supplier"
row["party"] = inv_data.get("party")
row["remarks"] = inv_data.get("remarks") row["remarks"] = inv_data.get("remarks")
row["gross_amount"]= item_details[0].get("gross_amount") row["gross_amount"]= item_details[0].get("gross_amount")
row["tax_amount"]= item_details[0].get("tax_amount") row["tax_amount"]= item_details[0].get("tax_amount")
@ -226,6 +228,20 @@ class VATAuditReport(object):
"options": "voucher_type", "options": "voucher_type",
"width": 150 "width": 150
}, },
{
"fieldname": "party_type",
"label": "Party Type",
"fieldtype": "Data",
"width": 140,
"hidden": 1
},
{
"fieldname": "party",
"label": "Party",
"fieldtype": "Dynamic Link",
"options": "party_type",
"width": 150
},
{ {
"fieldname": "remarks", "fieldname": "remarks",
"label": "Details", "label": "Details",
@ -236,18 +252,18 @@ class VATAuditReport(object):
"fieldname": "net_amount", "fieldname": "net_amount",
"label": "Net Amount", "label": "Net Amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 150 "width": 130
}, },
{ {
"fieldname": "tax_amount", "fieldname": "tax_amount",
"label": "Tax Amount", "label": "Tax Amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 150 "width": 130
}, },
{ {
"fieldname": "gross_amount", "fieldname": "gross_amount",
"label": "Gross Amount", "label": "Gross Amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 150 "width": 130
}, },
] ]

View File

@ -3,11 +3,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
# import frappe, os, json import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.permissions import add_permission, update_permission_property from frappe.permissions import add_permission, update_permission_property
def setup(company=None, patch=True): def setup(company=None, patch=True):
make_custom_fields()
add_permissions() add_permissions()
def make_custom_fields(update=True): def make_custom_fields(update=True):
@ -27,10 +28,23 @@ def make_custom_fields(update=True):
create_custom_fields(custom_fields, update=update) create_custom_fields(custom_fields, update=update)
def add_permissions(): def add_permissions():
"""Add Permissions for South Africa VAT Settings and South Africa VAT Account""" """Add Permissions for South Africa VAT Settings and South Africa VAT Account
and VAT Audit Report"""
for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'): for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'):
add_permission(doctype, 'All', 0) add_permission(doctype, 'All', 0)
for role in ('Accounts Manager', 'Accounts User', 'System Manager'): for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
add_permission(doctype, role, 0) add_permission(doctype, role, 0)
update_permission_property(doctype, role, 0, 'write', 1) update_permission_property(doctype, role, 0, 'write', 1)
update_permission_property(doctype, role, 0, 'create', 1) update_permission_property(doctype, role, 0, 'create', 1)
if not frappe.db.get_value('Custom Role', dict(report="VAT Audit Report")):
frappe.get_doc(dict(
doctype='Custom Role',
report="VAT Audit Report",
roles= [
dict(role='Accounts User'),
dict(role='Accounts Manager'),
dict(role='Auditor')
]
)).insert()

View File

@ -157,9 +157,7 @@ class Customer(TransactionBase):
'''If Customer created from Lead, update lead status to "Converted" '''If Customer created from Lead, update lead status to "Converted"
update Customer link in Quotation, Opportunity''' update Customer link in Quotation, Opportunity'''
if self.lead_name: if self.lead_name:
lead = frappe.get_doc('Lead', self.lead_name) frappe.db.set_value("Lead", self.lead_name, "status", "Converted")
lead.status = 'Converted'
lead.save()
def create_lead_address_contact(self): def create_lead_address_contact(self):
if self.lead_name: if self.lead_name:
@ -176,12 +174,12 @@ class Customer(TransactionBase):
address.append('links', dict(link_doctype='Customer', link_name=self.name)) address.append('links', dict(link_doctype='Customer', link_name=self.name))
address.save(ignore_permissions=self.flags.ignore_permissions) address.save(ignore_permissions=self.flags.ignore_permissions)
lead = frappe.db.get_value("Lead", self.lead_name, ["organization_lead", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) lead = frappe.db.get_value("Lead", self.lead_name, ["company_name", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True)
if not lead.lead_name: if not lead.lead_name:
frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name)) frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name))
if lead.organization_lead: if lead.company_name:
contact_names = frappe.get_all('Dynamic Link', filters={ contact_names = frappe.get_all('Dynamic Link', filters={
"parenttype":"Contact", "parenttype":"Contact",
"link_doctype":"Lead", "link_doctype":"Lead",

View File

@ -673,6 +673,8 @@ class TestSalesOrder(unittest.TestCase):
so.cancel() so.cancel()
dn.load_from_db()
self.assertRaises(frappe.CancelledLinkError, dn.submit) self.assertRaises(frappe.CancelledLinkError, dn.submit)
def test_service_type_product_bundle(self): def test_service_type_product_bundle(self):

View File

@ -6,24 +6,31 @@
"document_type": "Other", "document_type": "Other",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"customer_defaults_section",
"cust_master_name", "cust_master_name",
"campaign_naming_by",
"customer_group", "customer_group",
"column_break_4",
"territory", "territory",
"selling_price_list", "crm_settings_section",
"close_opportunity_after_days", "campaign_naming_by",
"default_valid_till", "default_valid_till",
"column_break_5", "column_break_9",
"close_opportunity_after_days",
"item_price_settings_section",
"selling_price_list",
"column_break_15",
"maintain_same_sales_rate",
"maintain_same_rate_action",
"editable_price_list_rate",
"validate_selling_price",
"sales_transactions_settings_section",
"so_required", "so_required",
"dn_required", "dn_required",
"sales_update_frequency", "sales_update_frequency",
"maintain_same_sales_rate", "column_break_5",
"maintain_same_rate_action",
"role_to_override_stop_action", "role_to_override_stop_action",
"editable_price_list_rate",
"allow_multiple_items", "allow_multiple_items",
"allow_against_multiple_purchase_orders", "allow_against_multiple_purchase_orders",
"validate_selling_price",
"hide_tax_id" "hide_tax_id"
], ],
"fields": [ "fields": [
@ -116,7 +123,7 @@
"default": "0", "default": "0",
"fieldname": "allow_multiple_items", "fieldname": "allow_multiple_items",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow Item to Be Added Multiple Times in a Transaction" "label": "Allow Item to be Added Multiple Times in a Transaction"
}, },
{ {
"default": "0", "default": "0",
@ -142,7 +149,7 @@
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.", "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
"fieldname": "maintain_same_rate_action", "fieldname": "maintain_same_rate_action",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Action If Same Rate is Not Maintained", "label": "Action if Same Rate is Not Maintained",
"mandatory_depends_on": "maintain_same_sales_rate", "mandatory_depends_on": "maintain_same_sales_rate",
"options": "Stop\nWarn" "options": "Stop\nWarn"
}, },
@ -152,6 +159,38 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Role Allowed to Override Stop Action", "label": "Role Allowed to Override Stop Action",
"options": "Role" "options": "Role"
},
{
"fieldname": "customer_defaults_section",
"fieldtype": "Section Break",
"label": "Customer Defaults"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "crm_settings_section",
"fieldtype": "Section Break",
"label": "CRM Settings"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "item_price_settings_section",
"fieldtype": "Section Break",
"label": "Item Price Settings"
},
{
"fieldname": "column_break_15",
"fieldtype": "Column Break"
},
{
"fieldname": "sales_transactions_settings_section",
"fieldtype": "Section Break",
"label": "Transaction Settings"
} }
], ],
"icon": "fa fa-cog", "icon": "fa fa-cog",
@ -159,7 +198,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-04-04 20:18:12.814624", "modified": "2021-08-06 22:25:50.119458",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Selling Settings", "name": "Selling Settings",

View File

@ -108,6 +108,9 @@ class Company(NestedSet):
frappe.flags.country_change = True frappe.flags.country_change = True
self.create_default_accounts() self.create_default_accounts()
self.create_default_warehouses() self.create_default_warehouses()
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}):
self.create_default_cost_center()
if frappe.flags.country_change: if frappe.flags.country_change:
install_country_fixtures(self.name, self.country) install_country_fixtures(self.name, self.country)
@ -117,9 +120,6 @@ class Company(NestedSet):
from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures
install_post_company_fixtures(frappe._dict({'company_name': self.name})) install_post_company_fixtures(frappe._dict({'company_name': self.name}))
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}):
self.create_default_cost_center()
if not frappe.local.flags.ignore_chart_of_accounts: if not frappe.local.flags.ignore_chart_of_accounts:
self.set_default_accounts() self.set_default_accounts()
if self.default_cash_account: if self.default_cash_account:

View File

@ -54,7 +54,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-05-08 23:13:48.049879", "modified": "2021-08-04 20:15:59.071493",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Transaction Deletion Record", "name": "Transaction Deletion Record",
@ -70,6 +70,7 @@
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],

View File

@ -124,7 +124,8 @@ def make_taxes_and_charges_template(company_name, doctype, template):
account_data = tax_row.get('account_head') account_data = tax_row.get('account_head')
tax_row_defaults = { tax_row_defaults = {
'category': 'Total', 'category': 'Total',
'charge_type': 'On Net Total' 'charge_type': 'On Net Total',
'cost_center': frappe.db.get_value('Company', company_name, 'cost_center')
} }
if doctype == 'Purchase Taxes and Charges Template': if doctype == 'Purchase Taxes and Charges Template':

View File

@ -269,11 +269,14 @@ class TestBatch(unittest.TestCase):
batch2 = create_batch('_Test Batch Price Item', 300, 1) batch2 = create_batch('_Test Batch Price Item', 300, 1)
batch3 = create_batch('_Test Batch Price Item', 400, 0) batch3 = create_batch('_Test Batch Price Item', 400, 0)
company = "_Test Company with perpetual inventory"
currency = frappe.get_cached_value("Company", company, "default_currency")
args = frappe._dict({ args = frappe._dict({
"item_code": "_Test Batch Price Item", "item_code": "_Test Batch Price Item",
"company": "_Test Company with perpetual inventory", "company": company,
"price_list": "_Test Price List", "price_list": "_Test Price List",
"currency": "_Test Currency", "currency": currency,
"doctype": "Sales Invoice", "doctype": "Sales Invoice",
"conversion_rate": 1, "conversion_rate": 1,
"price_list_currency": "_Test Currency", "price_list_currency": "_Test Currency",
@ -333,4 +336,4 @@ def make_new_batch(**args):
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
batch = frappe.get_doc("Batch", args.batch_id) batch = frappe.get_doc("Batch", args.batch_id)
return batch return batch

View File

@ -83,14 +83,17 @@ class TestItem(unittest.TestCase):
make_test_objects("Item Price") make_test_objects("Item Price")
company = "_Test Company"
currency = frappe.get_cached_value("Company", company, "default_currency")
details = get_item_details({ details = get_item_details({
"item_code": "_Test Item", "item_code": "_Test Item",
"company": "_Test Company", "company": company,
"price_list": "_Test Price List", "price_list": "_Test Price List",
"currency": "_Test Currency", "currency": currency,
"doctype": "Sales Order", "doctype": "Sales Order",
"conversion_rate": 1, "conversion_rate": 1,
"price_list_currency": "_Test Currency", "price_list_currency": currency,
"plc_conversion_rate": 1, "plc_conversion_rate": 1,
"order_type": "Sales", "order_type": "Sales",
"customer": "_Test Customer", "customer": "_Test Customer",

View File

@ -47,7 +47,8 @@
"description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nNumeric eg. 2: <b>mean &gt; 3.5</b> (mean of populated fields)<br>\nValue based eg.: <b>reading_value in (\"A\", \"B\", \"C\")</b>", "description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nNumeric eg. 2: <b>mean &gt; 3.5</b> (mean of populated fields)<br>\nValue based eg.: <b>reading_value in (\"A\", \"B\", \"C\")</b>",
"fieldname": "acceptance_formula", "fieldname": "acceptance_formula",
"fieldtype": "Code", "fieldtype": "Code",
"label": "Acceptance Criteria Formula" "label": "Acceptance Criteria Formula",
"options": "PythonExpression"
}, },
{ {
"default": "0", "default": "0",
@ -89,7 +90,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-02-04 18:50:02.056173", "modified": "2021-08-06 15:08:20.911338",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item Quality Inspection Parameter", "name": "Item Quality Inspection Parameter",

View File

@ -11,6 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.assets.doctype.asset.test_asset import create_asset_category, create_fixed_asset_item
class TestLandedCostVoucher(unittest.TestCase): class TestLandedCostVoucher(unittest.TestCase):
def test_landed_cost_voucher(self): def test_landed_cost_voucher(self):
@ -250,6 +251,39 @@ class TestLandedCostVoucher(unittest.TestCase):
self.assertEqual(entry.credit, amounts[0]) self.assertEqual(entry.credit, amounts[0])
self.assertEqual(entry.credit_in_account_currency, amounts[1]) self.assertEqual(entry.credit_in_account_currency, amounts[1])
def test_asset_lcv(self):
"Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly."
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC")
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
if not frappe.db.exists("Item", "Macbook Pro"):
create_fixed_asset_item()
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=50000)
# check if draft asset was created
assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
self.assertEqual(len(assets), 1)
lcv = make_landed_cost_voucher(
company = pr.company,
receipt_document_type = "Purchase Receipt",
receipt_document=pr.name,
charges=80,
expense_account="Expenses Included In Valuation - _TC")
lcv.save()
lcv.submit()
# lcv updates amount in draft asset
self.assertEqual(frappe.db.get_value("Asset", assets[0].name, "gross_purchase_amount"), 50080)
# tear down
lcv.cancel()
pr.cancel()
def make_landed_cost_voucher(** args): def make_landed_cost_voucher(** args):
args = frappe._dict(args) args = frappe._dict(args)
ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document) ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document)
@ -268,7 +302,7 @@ def make_landed_cost_voucher(** args):
lcv.set("taxes", [{ lcv.set("taxes", [{
"description": "Shipping Charges", "description": "Shipping Charges",
"expense_account": "Expenses Included In Valuation - TCP1", "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1",
"amount": args.charges "amount": args.charges
}]) }])

View File

@ -290,8 +290,16 @@ class PurchaseReceipt(BuyingController):
and warehouse_account_name == supplier_warehouse_account: and warehouse_account_name == supplier_warehouse_account:
continue continue
self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, self.add_gl_entry(
stock_rbnb, account_currency=warehouse_account_currency, item=d) gl_entries=gl_entries,
account=warehouse_account_name,
cost_center=d.cost_center,
debit=stock_value_diff,
credit=0.0,
remarks=remarks,
against_account=stock_rbnb,
account_currency=warehouse_account_currency,
item=d)
# GL Entry for from warehouse or Stock Received but not billed # GL Entry for from warehouse or Stock Received but not billed
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation # Intentionally passed negative debit amount to avoid incorrect GL Entry validation
@ -304,9 +312,17 @@ class PurchaseReceipt(BuyingController):
account = warehouse_account[d.from_warehouse]['account'] \ account = warehouse_account[d.from_warehouse]['account'] \
if d.from_warehouse else stock_rbnb if d.from_warehouse else stock_rbnb
self.add_gl_entry(gl_entries, account, d.cost_center, self.add_gl_entry(
-1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, gl_entries=gl_entries,
debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) account=account,
cost_center=d.cost_center,
debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")),
credit=0.0,
remarks=remarks,
against_account=warehouse_account_name,
debit_in_account_currency=-1 * credit_amount,
account_currency=credit_currency,
item=d)
# check if the exchange rate has changed # check if the exchange rate has changed
if d.get('purchase_invoice'): if d.get('purchase_invoice'):
@ -317,13 +333,29 @@ class PurchaseReceipt(BuyingController):
discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * \ discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * \
(exchange_rate_map[d.purchase_invoice] - self.conversion_rate) (exchange_rate_map[d.purchase_invoice] - self.conversion_rate)
self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference, self.add_gl_entry(
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, gl_entries=gl_entries,
account_currency=credit_currency, item=d) account=account,
cost_center=d.cost_center,
debit=0.0,
credit=discrepancy_caused_by_exchange_rate_difference,
remarks=remarks,
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency,
item=d)
self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0, self.add_gl_entry(
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, gl_entries=gl_entries,
account_currency=credit_currency, item=d) account=self.get_company_default("exchange_gain_loss_account"),
cost_center=d.cost_center,
debit=discrepancy_caused_by_exchange_rate_difference,
credit=0.0,
remarks=remarks,
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency,
item=d)
# Amount added through landed-cos-voucher # Amount added through landed-cos-voucher
if d.landed_cost_voucher_amount and landed_cost_entries: if d.landed_cost_voucher_amount and landed_cost_entries:
@ -332,14 +364,31 @@ class PurchaseReceipt(BuyingController):
credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or
account_currency!=self.company_currency) else flt(amount["amount"])) account_currency!=self.company_currency) else flt(amount["amount"]))
self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks, self.add_gl_entry(
warehouse_account_name, credit_in_account_currency=flt(amount["amount"]), gl_entries=gl_entries,
account_currency=account_currency, project=d.project, item=d) account=account,
cost_center=d.cost_center,
debit=0.0,
credit=credit_amount,
remarks=remarks,
against_account=warehouse_account_name,
credit_in_account_currency=flt(amount["amount"]),
account_currency=account_currency,
project=d.project,
item=d)
# sub-contracting warehouse # sub-contracting warehouse
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost), self.add_gl_entry(
remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d) gl_entries=gl_entries,
account=supplier_warehouse_account,
cost_center=d.cost_center,
debit=0.0,
credit=flt(d.rm_supp_cost),
remarks=remarks,
against_account=warehouse_account_name,
account_currency=supplier_warehouse_account_currency,
item=d)
# divisional loss adjustment # divisional loss adjustment
valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \ valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \
@ -356,8 +405,17 @@ class PurchaseReceipt(BuyingController):
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks, self.add_gl_entry(
warehouse_account_name, account_currency=credit_currency, project=d.project, item=d) gl_entries=gl_entries,
account=loss_account,
cost_center=cost_center,
debit=divisional_loss,
credit=0.0,
remarks=remarks,
against_account=warehouse_account_name,
account_currency=credit_currency,
project=d.project,
item=d)
elif d.warehouse not in warehouse_with_no_account or \ elif d.warehouse not in warehouse_with_no_account or \
d.rejected_warehouse not in warehouse_with_no_account: d.rejected_warehouse not in warehouse_with_no_account:
@ -368,12 +426,30 @@ class PurchaseReceipt(BuyingController):
debit_currency = get_account_currency(d.expense_account) debit_currency = get_account_currency(d.expense_account)
remarks = self.get("remarks") or _("Accounting Entry for Service") remarks = self.get("remarks") or _("Accounting Entry for Service")
self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount, self.add_gl_entry(
remarks, d.expense_account, account_currency=credit_currency, project=d.project, gl_entries=gl_entries,
account=service_received_but_not_billed_account,
cost_center=d.cost_center,
debit=0.0,
credit=d.amount,
remarks=remarks,
against_account=d.expense_account,
account_currency=credit_currency,
project=d.project,
voucher_detail_no=d.name, item=d) voucher_detail_no=d.name, item=d)
self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account, self.add_gl_entry(
account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d) gl_entries=gl_entries,
account=d.expense_account,
cost_center=d.cost_center,
debit=d.amount,
credit=0.0,
remarks=remarks,
against_account=service_received_but_not_billed_account,
account_currency = debit_currency,
project=d.project,
voucher_detail_no=d.name,
item=d)
if warehouse_with_no_account: if warehouse_with_no_account:
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
@ -423,8 +499,15 @@ class PurchaseReceipt(BuyingController):
applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
amount_including_divisional_loss -= applicable_amount amount_including_divisional_loss -= applicable_amount
self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"), self.add_gl_entry(
against_account, item=tax) gl_entries=gl_entries,
account=account,
cost_center=tax.cost_center,
debit=0.0,
credit=applicable_amount,
remarks=self.remarks or _("Accounting Entry for Stock"),
against_account=against_account,
item=tax)
i += 1 i += 1
@ -477,15 +560,31 @@ class PurchaseReceipt(BuyingController):
# debit cwip account # debit cwip account
debit_in_account_currency = (base_asset_amount debit_in_account_currency = (base_asset_amount
if cwip_account_currency == self.company_currency else asset_amount) if cwip_account_currency == self.company_currency else asset_amount)
self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks, self.add_gl_entry(
arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item) gl_entries=gl_entries,
account=cwip_account,
cost_center=item.cost_center,
debit=base_asset_amount,
credit=0.0,
remarks=remarks,
against_account=arbnb_account,
debit_in_account_currency=debit_in_account_currency,
item=item)
asset_rbnb_currency = get_account_currency(arbnb_account) asset_rbnb_currency = get_account_currency(arbnb_account)
# credit arbnb account # credit arbnb account
credit_in_account_currency = (base_asset_amount credit_in_account_currency = (base_asset_amount
if asset_rbnb_currency == self.company_currency else asset_amount) if asset_rbnb_currency == self.company_currency else asset_amount)
self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks, self.add_gl_entry(
cwip_account, credit_in_account_currency=credit_in_account_currency, item=item) gl_entries=gl_entries,
account=arbnb_account,
cost_center=item.cost_center,
debit=0.0,
credit=base_asset_amount,
remarks=remarks,
against_account=cwip_account,
credit_in_account_currency=credit_in_account_currency,
item=item)
def add_lcv_gl_entries(self, item, gl_entries): def add_lcv_gl_entries(self, item, gl_entries):
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
@ -498,11 +597,27 @@ class PurchaseReceipt(BuyingController):
remarks = self.get("remarks") or _("Accounting Entry for Stock") remarks = self.get("remarks") or _("Accounting Entry for Stock")
self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), self.add_gl_entry(
remarks, asset_account, project=item.project, item=item) gl_entries=gl_entries,
account=expenses_included_in_asset_valuation,
cost_center=item.cost_center,
debit=0.0,
credit=flt(item.landed_cost_voucher_amount),
remarks=remarks,
against_account=asset_account,
project=item.project,
item=item)
self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), self.add_gl_entry(
remarks, expenses_included_in_asset_valuation, project=item.project, item=item) gl_entries=gl_entries,
account=asset_account,
cost_center=item.cost_center,
debit=flt(item.landed_cost_voucher_amount),
credit=0.0,
remarks=remarks,
against_account=expenses_included_in_asset_valuation,
project=item.project,
item=item)
def update_assets(self, item, valuation_rate): def update_assets(self, item, valuation_rate):
assets = frappe.db.get_all('Asset', assets = frappe.db.get_all('Asset',

View File

@ -23,9 +23,7 @@ class TestPurchaseReceipt(unittest.TestCase):
def test_reverse_purchase_receipt_sle(self): def test_reverse_purchase_receipt_sle(self):
frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0) pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
pr = make_purchase_receipt(qty=0.5)
sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
"voucher_no": pr.name}, ['actual_qty']) "voucher_no": pr.name}, ['actual_qty'])
@ -41,8 +39,6 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEqual(len(sl_entry_cancelled), 2) self.assertEqual(len(sl_entry_cancelled), 2)
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5) self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1)
def test_make_purchase_invoice(self): def test_make_purchase_invoice(self):
if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'): if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'):
frappe.get_doc({ frappe.get_doc({
@ -328,21 +324,8 @@ class TestPurchaseReceipt(unittest.TestCase):
pr1.submit() pr1.submit()
self.assertRaises(frappe.ValidationError, pr2.submit) self.assertRaises(frappe.ValidationError, pr2.submit)
frappe.db.rollback()
pr1.cancel()
se.cancel()
se1.cancel()
se2.cancel()
se3.cancel()
po.reload()
pr2.load_from_db()
if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry',
{'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'):
pr2.cancel()
po.load_from_db()
po.cancel()
def test_serial_no_supplier(self): def test_serial_no_supplier(self):
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)

View File

@ -165,8 +165,14 @@ class SerialNo(StockController):
) )
ORDER BY ORDER BY
posting_date desc, posting_time desc, creation desc""", posting_date desc, posting_time desc, creation desc""",
(self.item_code, self.company, (
serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'), as_dict=1): self.item_code, self.company,
serial_no,
serial_no+'\n%',
'%\n'+serial_no,
'%\n'+serial_no+'\n%'
),
as_dict=1):
if serial_no.upper() in get_serial_nos(sle.serial_no): if serial_no.upper() in get_serial_nos(sle.serial_no):
if cint(sle.actual_qty) > 0: if cint(sle.actual_qty) > 0:
sle_dict.setdefault("incoming", []).append(sle) sle_dict.setdefault("incoming", []).append(sle)

View File

@ -174,5 +174,23 @@ class TestSerialNo(unittest.TestCase):
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
self.assertEqual(sn_doc.purchase_document_no, se.name) self.assertEqual(sn_doc.purchase_document_no, se.name)
def test_serial_no_sanitation(self):
"Test if Serial No input is sanitised before entering the DB."
item_code = "_Test Serialized Item"
test_records = frappe.get_test_records('Stock Entry')
se = frappe.copy_doc(test_records[0])
se.get("items")[0].item_code = item_code
se.get("items")[0].qty = 3
se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 "
se.get("items")[0].transfer_qty = 3
se.set_stock_entry_type()
se.insert()
se.submit()
self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3")
frappe.db.rollback()
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()

View File

@ -76,6 +76,7 @@ class StockEntry(StockController):
self.validate_difference_account() self.validate_difference_account()
self.set_job_card_data() self.set_job_card_data()
self.set_purpose_for_stock_entry() self.set_purpose_for_stock_entry()
self.clean_serial_nos()
self.validate_duplicate_serial_no() self.validate_duplicate_serial_no()
if not self.from_bom: if not self.from_bom:

View File

@ -55,8 +55,8 @@ class StockLedgerEntry(Document):
"sum(actual_qty)") or 0 "sum(actual_qty)") or 0
frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty) frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty)
#check for item quantity available in stock
def actual_amt_check(self): def actual_amt_check(self):
"""Validate that qty at warehouse for selected batch is >=0"""
if self.batch_no and not self.get("allow_negative_stock"): if self.batch_no and not self.get("allow_negative_stock"):
batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty) batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry` from `tabStock Ledger Entry`
@ -107,7 +107,7 @@ class StockLedgerEntry(Document):
self.stock_uom = item_det.stock_uom self.stock_uom = item_det.stock_uom
def check_stock_frozen_date(self): def check_stock_frozen_date(self):
stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings') stock_settings = frappe.get_cached_doc('Stock Settings')
if stock_settings.stock_frozen_upto: if stock_settings.stock_frozen_upto:
if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto) if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto)

View File

@ -31,6 +31,7 @@ class StockReconciliation(StockController):
self.validate_expense_account() self.validate_expense_account()
self.validate_customer_provided_item() self.validate_customer_provided_item()
self.set_zero_value_for_customer_provided_items() self.set_zero_value_for_customer_provided_items()
self.clean_serial_nos()
self.set_total_qty_and_amount() self.set_total_qty_and_amount()
self.validate_putaway_capacity() self.validate_putaway_capacity()

View File

@ -74,8 +74,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
update_party_blanket_order(args, out) update_party_blanket_order(args, out)
out.update(get_price_list_rate(args, item))
get_price_list_rate(args, item, out)
if args.customer and cint(args.is_pos): if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args, update_data=True)) out.update(get_pos_profile_item_details(args.company, args, update_data=True))
@ -638,7 +637,10 @@ def get_default_supplier(args, item, item_group, brand):
or item_group.get("default_supplier") or item_group.get("default_supplier")
or brand.get("default_supplier")) or brand.get("default_supplier"))
def get_price_list_rate(args, item_doc, out): def get_price_list_rate(args, item_doc, out=None):
if out is None:
out = frappe._dict()
meta = frappe.get_meta(args.parenttype or args.doctype) meta = frappe.get_meta(args.parenttype or args.doctype)
if meta.get_field("currency") or args.get('currency'): if meta.get_field("currency") or args.get('currency'):
@ -651,17 +653,17 @@ def get_price_list_rate(args, item_doc, out):
if meta.get_field("currency"): if meta.get_field("currency"):
validate_conversion_rate(args, meta) validate_conversion_rate(args, meta)
price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0 price_list_rate = get_price_list_rate_for(args, item_doc.name)
# variant # variant
if not price_list_rate and item_doc.variant_of: if price_list_rate is None and item_doc.variant_of:
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
# insert in database # insert in database
if not price_list_rate: if price_list_rate is None:
if args.price_list and args.rate: if args.price_list and args.rate:
insert_item_price(args) insert_item_price(args)
return {} return out
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \ out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
/ flt(args.conversion_rate) / flt(args.conversion_rate)
@ -671,6 +673,8 @@ def get_price_list_rate(args, item_doc, out):
out.update(get_last_purchase_details(item_doc.name, out.update(get_last_purchase_details(item_doc.name,
args.name, args.conversion_rate)) args.name, args.conversion_rate))
return out
def insert_item_price(args): def insert_item_price(args):
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same""" """Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \ if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \
@ -1073,9 +1077,8 @@ def apply_price_list(args, as_doc=False):
} }
def apply_price_list_on_item(args): def apply_price_list_on_item(args):
item_details = frappe._dict()
item_doc = frappe.get_doc("Item", args.item_code) item_doc = frappe.get_doc("Item", args.item_code)
get_price_list_rate(args, item_doc, item_details) item_details = get_price_list_rate(args, item_doc)
item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate)) item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate))

View File

@ -279,15 +279,13 @@ class update_entries_after(object):
} }
""" """
self.data.setdefault(args.warehouse, frappe._dict())
warehouse_dict = self.data[args.warehouse]
previous_sle = get_previous_sle_of_current_voucher(args) previous_sle = get_previous_sle_of_current_voucher(args)
warehouse_dict.previous_sle = previous_sle
for key in ("qty_after_transaction", "valuation_rate", "stock_value"): self.data[args.warehouse] = frappe._dict({
setattr(warehouse_dict, key, flt(previous_sle.get(key))) "previous_sle": previous_sle,
"qty_after_transaction": flt(previous_sle.qty_after_transaction),
warehouse_dict.update({ "valuation_rate": flt(previous_sle.valuation_rate),
"stock_value": flt(previous_sle.stock_value),
"prev_stock_value": previous_sle.stock_value or 0.0, "prev_stock_value": previous_sle.stock_value or 0.0,
"stock_queue": json.loads(previous_sle.stock_queue or "[]"), "stock_queue": json.loads(previous_sle.stock_queue or "[]"),
"stock_value_difference": 0.0 "stock_value_difference": 0.0

View File

@ -224,7 +224,7 @@ def get_avg_purchase_rate(serial_nos):
def get_valuation_method(item_code): def get_valuation_method(item_code):
"""get valuation method from item or default""" """get valuation method from item or default"""
val_method = frappe.db.get_value('Item', item_code, 'valuation_method') val_method = frappe.db.get_value('Item', item_code, 'valuation_method', cache=True)
if not val_method: if not val_method:
val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO" val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO"
return val_method return val_method
@ -275,17 +275,17 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
return valid_serial_nos return valid_serial_nos
def validate_warehouse_company(warehouse, company): def validate_warehouse_company(warehouse, company):
warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company") warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company", cache=True)
if warehouse_company and warehouse_company != company: if warehouse_company and warehouse_company != company:
frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company), frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company),
InvalidWarehouseCompany) InvalidWarehouseCompany)
def is_group_warehouse(warehouse): def is_group_warehouse(warehouse):
if frappe.db.get_value("Warehouse", warehouse, "is_group"): if frappe.db.get_value("Warehouse", warehouse, "is_group", cache=True):
frappe.throw(_("Group node warehouse is not allowed to select for transactions")) frappe.throw(_("Group node warehouse is not allowed to select for transactions"))
def validate_disabled_warehouse(warehouse): def validate_disabled_warehouse(warehouse):
if frappe.db.get_value("Warehouse", warehouse, "disabled"): if frappe.db.get_value("Warehouse", warehouse, "disabled", cache=True):
frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse))) frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse)))
def update_included_uom_in_report(columns, result, include_uom, conversion_factors): def update_included_uom_in_report(columns, result, include_uom, conversion_factors):

View File

@ -116,6 +116,10 @@ class Issue(Document):
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
return replicated_issue.name return replicated_issue.name
def reset_issue_metrics(self):
self.db_set("resolution_time", None)
self.db_set("user_resolution_time", None)
def get_list_context(context=None): def get_list_context(context=None):
return { return {

View File

@ -281,15 +281,18 @@ def get_repeated(values):
def get_documents_with_active_service_level_agreement(): def get_documents_with_active_service_level_agreement():
if not frappe.cache().hget("service_level_agreement", "active"): sla_doctypes = frappe.cache().hget("service_level_agreement", "active")
set_documents_with_active_service_level_agreement()
return frappe.cache().hget("service_level_agreement", "active") if sla_doctypes is None:
return set_documents_with_active_service_level_agreement()
return sla_doctypes
def set_documents_with_active_service_level_agreement(): def set_documents_with_active_service_level_agreement():
active = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])] active = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])]
frappe.cache().hset("service_level_agreement", "active", active) frappe.cache().hset("service_level_agreement", "active", active)
return active
def apply(doc, method=None): def apply(doc, method=None):