Merge branch 'develop' into payment-reconc-party-validation-fix

This commit is contained in:
Afshan
2021-08-11 11:49:27 +05:30
committed by GitHub
30 changed files with 222 additions and 75 deletions

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,8 +73,12 @@ 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):
"""Validate that profit and loss type account GL entries have a 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": if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
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( msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
self.voucher_type, self.voucher_no, self.account) self.voucher_type, self.voucher_no, self.account)
msg += " " msg += " "

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 = {}
@@ -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

@@ -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,9 +145,12 @@ 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"))
if cwip_enabled:
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount 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""")] where account_type = 'Capital Work in Progress' and is_group=0""")]

View File

@@ -922,7 +922,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 = ""
@@ -938,30 +937,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):
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) 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

@@ -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))

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

@@ -294,6 +294,7 @@ 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.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

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

@@ -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

@@ -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'),

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

@@ -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:

View File

@@ -109,6 +109,9 @@ class Company(NestedSet):
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)
self.create_default_tax_template() self.create_default_tax_template()
@@ -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

@@ -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

@@ -253,6 +253,8 @@ class TestLandedCostVoucher(unittest.TestCase):
def test_asset_lcv(self): def test_asset_lcv(self):
"Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly." "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"): if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category() create_asset_category()
@@ -265,7 +267,6 @@ class TestLandedCostVoucher(unittest.TestCase):
assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
self.assertEqual(len(assets), 1) self.assertEqual(len(assets), 1)
frappe.db.set_value("Company", pr.company, "capital_work_in_progress_account", "CWIP Account - _TC")
lcv = make_landed_cost_voucher( lcv = make_landed_cost_voucher(
company = pr.company, company = pr.company,
receipt_document_type = "Purchase Receipt", receipt_document_type = "Purchase Receipt",

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

@@ -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

@@ -117,6 +117,10 @@ class Issue(Document):
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 {
"title": _("Issues"), "title": _("Issues"),