mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-17 05:45:11 +00:00
Merge branch 'v12-pre-release' into version-12
This commit is contained in:
@@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '12.12.1'
|
||||
__version__ = '12.13.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"default": "1",
|
||||
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unlink Advance Payment on Cancelation of Order"
|
||||
"label": "Unlink Advance Payment on Cancellation of Order"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
@@ -179,7 +179,7 @@
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"modified": "2020-03-11 13:09:26.235848",
|
||||
"modified": "2020-10-08 09:40:12.121145",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -1,347 +1,122 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:year",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-22 16:50:25",
|
||||
"custom": 0,
|
||||
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"allow_import": 1,
|
||||
"autoname": "field:year",
|
||||
"creation": "2013-01-22 16:50:25",
|
||||
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"year",
|
||||
"disabled",
|
||||
"is_short_year",
|
||||
"year_start_date",
|
||||
"year_end_date",
|
||||
"companies",
|
||||
"auto_created"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "For e.g. 2012, 2012-13",
|
||||
"fieldname": "year",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Year Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "year",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"description": "For e.g. 2012, 2012-13",
|
||||
"fieldname": "year",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Year Name",
|
||||
"oldfieldname": "year",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "year_start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Year Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "year_start_date",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "year_start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Year Start Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "year_start_date",
|
||||
"oldfieldtype": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "year_end_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Year End Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "year_end_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Year End Date",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "companies",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Companies",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Fiscal Year Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "companies",
|
||||
"fieldtype": "Table",
|
||||
"label": "Companies",
|
||||
"options": "Fiscal Year Company"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "auto_created",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Auto Created",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"default": "0",
|
||||
"fieldname": "auto_created",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Auto Created",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Less than 12 months.",
|
||||
"fieldname": "is_short_year",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Short Year",
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-25 14:21:41.273354",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"modified": "2020-10-03 18:22:04.161315",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
"read": 1,
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Purchase User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
"read": 1,
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
"read": 1,
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Stock User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
"read": 1,
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Employee",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"read": 1,
|
||||
"role": "Employee"
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "name",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "name",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -36,6 +36,11 @@ class FiscalYear(Document):
|
||||
frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."))
|
||||
|
||||
def validate_dates(self):
|
||||
if self.is_short_year:
|
||||
# Fiscal Year can be shorter than one year, in some jurisdictions
|
||||
# under certain circumstances. For example, in the USA and Germany.
|
||||
return
|
||||
|
||||
if getdate(self.year_start_date) > getdate(self.year_end_date):
|
||||
frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
|
||||
FiscalYearIncorrectDate)
|
||||
|
||||
@@ -58,5 +58,12 @@
|
||||
"year": "_Test Fiscal Year 2021",
|
||||
"year_end_date": "2021-12-31",
|
||||
"year_start_date": "2021-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Short Fiscal Year 2021",
|
||||
"is_short_year": 1,
|
||||
"year_end_date": "2021-12-31",
|
||||
"year_start_date": "2021-04-01"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -31,13 +31,19 @@ apply_on_table = {
|
||||
}
|
||||
|
||||
def get_pricing_rules(args, doc=None):
|
||||
pricing_rules = []
|
||||
values = {}
|
||||
pricing_rules_all = []
|
||||
values = {}
|
||||
|
||||
for apply_on in ['Item Code', 'Item Group', 'Brand']:
|
||||
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
|
||||
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
|
||||
break
|
||||
pricing_rules_all.extend(_get_pricing_rules(apply_on, args, values))
|
||||
|
||||
# removing duplicate pricing rule
|
||||
pricing_rules_name = []
|
||||
pricing_rules = []
|
||||
for p in pricing_rules_all:
|
||||
if p['name'] not in pricing_rules_name:
|
||||
pricing_rules_name.append(p['name'])
|
||||
pricing_rules.append(p)
|
||||
|
||||
rules = []
|
||||
|
||||
@@ -323,9 +329,10 @@ def apply_internal_priority(pricing_rules, field_set, args):
|
||||
filtered_rules = []
|
||||
for field in field_set:
|
||||
if args.get(field):
|
||||
# filter function always returns a filter object even if empty
|
||||
# list conversion is necessary to check for an empty result
|
||||
filtered_rules = list(filter(lambda x: x.get(field)==args.get(field), pricing_rules))
|
||||
for rule in pricing_rules:
|
||||
if rule.get(field) == args.get(field):
|
||||
filtered_rules = [rule]
|
||||
break
|
||||
if filtered_rules: break
|
||||
|
||||
return filtered_rules or pricing_rules
|
||||
|
||||
@@ -636,7 +636,8 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount / self.conversion_rate)
|
||||
}, item=item))
|
||||
else:
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||
asset_category=item.asset_category,company=self.company)
|
||||
|
||||
cwip_account_currency = get_account_currency(cwip_account)
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
|
||||
@@ -939,7 +939,8 @@ def make_purchase_invoice(**args):
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"project": args.project,
|
||||
"rejected_warehouse": args.rejected_warehouse or "",
|
||||
"rejected_serial_no": args.rejected_serial_no or ""
|
||||
"rejected_serial_no": args.rejected_serial_no or "",
|
||||
"asset_location": args.location or ""
|
||||
})
|
||||
|
||||
if args.get_taxes_and_charges:
|
||||
|
||||
@@ -13,8 +13,7 @@ def get_data():
|
||||
'Auto Repeat': 'reference_document',
|
||||
},
|
||||
'internal_links': {
|
||||
'Sales Order': ['items', 'sales_order'],
|
||||
'Delivery Note': ['items', 'delivery_note']
|
||||
'Sales Order': ['items', 'sales_order']
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
|
||||
@@ -263,13 +263,14 @@ class Subscription(Document):
|
||||
invoice.set_taxes()
|
||||
|
||||
# Due date
|
||||
invoice.append(
|
||||
'payment_schedule',
|
||||
{
|
||||
'due_date': add_days(self.current_invoice_end, cint(self.days_until_due)),
|
||||
'invoice_portion': 100
|
||||
}
|
||||
)
|
||||
if self.days_until_due:
|
||||
invoice.append(
|
||||
'payment_schedule',
|
||||
{
|
||||
'due_date': add_days(self.current_invoice_end, cint(self.days_until_due)),
|
||||
'invoice_portion': 100
|
||||
}
|
||||
)
|
||||
|
||||
# Discounts
|
||||
if self.additional_discount_percentage:
|
||||
|
||||
@@ -210,6 +210,7 @@ class TestSubscription(unittest.TestCase):
|
||||
subscription.customer = '_Test Customer'
|
||||
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
|
||||
subscription.start = '2018-01-01'
|
||||
subscription.days_until_due = 1
|
||||
subscription.insert()
|
||||
subscription.process() # generate first invoice
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from erpnext.accounts.doctype.tax_rule.tax_rule import IncorrectCustomerGroup, IncorrectSupplierType, ConflictingTaxRule, get_tax_template
|
||||
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
|
||||
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
|
||||
|
||||
test_records = frappe.get_test_records('Tax Rule')
|
||||
|
||||
@@ -144,6 +146,23 @@ class TestTaxRule(unittest.TestCase):
|
||||
self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
|
||||
"_Test Sales Taxes and Charges Template 1 - _TC")
|
||||
|
||||
def test_taxes_fetch_via_tax_rule(self):
|
||||
make_tax_rule(customer= "_Test Customer", billing_city = "_Test City",
|
||||
sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
|
||||
|
||||
# create opportunity for customer
|
||||
opportunity = make_opportunity(with_items=1)
|
||||
|
||||
# make quotation from opportunity
|
||||
quotation = make_quotation(opportunity.name)
|
||||
quotation.save()
|
||||
|
||||
self.assertEqual(quotation.taxes_and_charges, "_Test Sales Taxes and Charges Template - _TC")
|
||||
|
||||
# Check if accounts heads and rate fetched are also fetched from tax template or not
|
||||
self.assertTrue(len(quotation.taxes) > 0)
|
||||
|
||||
|
||||
|
||||
def make_tax_rule(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -106,6 +106,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai
|
||||
from `tabGL Entry`
|
||||
where company = %s and
|
||||
party in %s and fiscal_year=%s and credit > 0
|
||||
and is_opening = 'No'
|
||||
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
|
||||
|
||||
vouchers = [d.voucher_no for d in entries]
|
||||
@@ -139,9 +140,9 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai
|
||||
else:
|
||||
tds_amount = _get_tds(net_total, tax_details.rate)
|
||||
else:
|
||||
supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
|
||||
fields = ['sum(net_amount)'],
|
||||
filters = {'parent': ('in', vouchers), 'docstatus': 1}, as_list=1)
|
||||
supplier_credit_amount = frappe.get_all('Purchase Invoice',
|
||||
fields = ['sum(net_total)'],
|
||||
filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1)
|
||||
|
||||
supplier_credit_amount = (supplier_credit_amount[0][0]
|
||||
if supplier_credit_amount and supplier_credit_amount[0][0] else 0)
|
||||
@@ -192,6 +193,7 @@ def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=No
|
||||
select distinct voucher_no
|
||||
from `tabGL Entry`
|
||||
where party in %s and %s and debit > 0
|
||||
and is_opening = 'No'
|
||||
""", (tuple(suppliers), condition)) or []
|
||||
|
||||
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
|
||||
|
||||
@@ -101,6 +101,29 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
|
||||
invoices = []
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS")
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
# TDS not applied
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier2", do_not_apply_tds=True)
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
|
||||
self.assertEqual(pi.grand_total, 8000)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def create_purchase_invoice(**args):
|
||||
# return sales invoice doc object
|
||||
item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
|
||||
@@ -109,7 +132,7 @@ def create_purchase_invoice(**args):
|
||||
pi = frappe.get_doc({
|
||||
"doctype": "Purchase Invoice",
|
||||
"posting_date": today(),
|
||||
"apply_tds": 1,
|
||||
"apply_tds": 0 if args.do_not_apply_tds else 1,
|
||||
"supplier": args.supplier,
|
||||
"company": '_Test Company',
|
||||
"taxes_and_charges": "",
|
||||
|
||||
@@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
|
||||
check_plaid_status() {
|
||||
const me = this;
|
||||
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
|
||||
if (r && r.enabled == "1") {
|
||||
if (r && r.enabled === "1") {
|
||||
me.plaid_status = "active"
|
||||
} else {
|
||||
me.plaid_status = "inactive"
|
||||
@@ -139,7 +139,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
|
||||
}
|
||||
|
||||
make() {
|
||||
const me = this;
|
||||
const me = this;
|
||||
new frappe.ui.FileUploader({
|
||||
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
|
||||
allow_multiple: 0,
|
||||
@@ -214,31 +214,35 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {
|
||||
|
||||
init_config() {
|
||||
const me = this;
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
||||
.then(result => {
|
||||
me.plaid_env = result.plaid_env;
|
||||
me.plaid_public_key = result.plaid_public_key;
|
||||
me.client_name = result.client_name;
|
||||
me.sync_transactions()
|
||||
})
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
|
||||
.then(result => {
|
||||
me.plaid_env = result.plaid_env;
|
||||
me.client_name = result.client_name;
|
||||
me.link_token = result.link_token;
|
||||
me.sync_transactions();
|
||||
})
|
||||
}
|
||||
|
||||
sync_transactions() {
|
||||
const me = this;
|
||||
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
|
||||
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
||||
bank: v['bank'],
|
||||
bank: r.bank,
|
||||
bank_account: me.parent.bank_account,
|
||||
freeze: true
|
||||
})
|
||||
.then((result) => {
|
||||
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
|
||||
let result_title = (result && result.length > 0)
|
||||
? __("{0} bank transaction(s) created", [result.length])
|
||||
: __("This bank account is already synchronized");
|
||||
|
||||
let result_msg = `
|
||||
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
|
||||
<h5 class="text-muted">${result_title}</h5>
|
||||
</div>`
|
||||
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
|
||||
<h5 class="text-muted">${result_title}</h5>
|
||||
</div>`
|
||||
|
||||
this.parent.$main_section.append(result_msg)
|
||||
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
|
||||
frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -384,7 +388,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
|
||||
})
|
||||
|
||||
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
|
||||
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
|
||||
{ bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
|
||||
).then((result) => {
|
||||
me.make_dialog(result)
|
||||
})
|
||||
|
||||
@@ -1064,7 +1064,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
$(frappe.render_template("pos_item", {
|
||||
item_code: escape(obj.name),
|
||||
item_price: item_price,
|
||||
title: obj.name || obj.item_name,
|
||||
title: obj.name === obj.item_name ? obj.name : obj.item_name,
|
||||
item_name: obj.name === obj.item_name ? "" : obj.item_name,
|
||||
item_image: obj.image,
|
||||
item_stock: __('Stock Qty') + ": " + me.get_actual_qty(obj),
|
||||
@@ -1546,7 +1546,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
$.each(this.frm.doc.items || [], function (i, d) {
|
||||
$(frappe.render_template("pos_bill_item_new", {
|
||||
item_code: escape(d.item_code),
|
||||
title: d.item_code || d.item_name,
|
||||
title: d.item_code === d.item_name ? d.item_code : d.item_name,
|
||||
item_name: (d.item_name === d.item_code || !d.item_name) ? "" : ("<br>" + d.item_name),
|
||||
qty: d.qty,
|
||||
discount_percentage: d.discount_percentage || 0.0,
|
||||
|
||||
@@ -69,7 +69,7 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
filters: {
|
||||
'company': company
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -617,9 +617,19 @@ class ReceivablePayableReport(object):
|
||||
elif party_type_field=="supplier":
|
||||
self.add_supplier_filters(conditions, values)
|
||||
|
||||
if self.filters.cost_center:
|
||||
self.get_cost_center_conditions(conditions)
|
||||
|
||||
self.add_accounting_dimensions_filters(conditions, values)
|
||||
return " and ".join(conditions), values
|
||||
|
||||
def get_cost_center_conditions(self, conditions):
|
||||
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
|
||||
cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
|
||||
|
||||
cost_center_string = '", "'.join(cost_center_list)
|
||||
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
|
||||
|
||||
def get_order_by_condition(self):
|
||||
if self.filters.get('group_by_party'):
|
||||
return "order by party, posting_date"
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
|
||||
frappe.query_reports["Bank Reconciliation Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname":"account",
|
||||
"label": __("Bank Account"),
|
||||
@@ -12,11 +20,14 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
|
||||
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
|
||||
"reqd": 1,
|
||||
"get_query": function() {
|
||||
var company = frappe.query_report.get_filter_value('company')
|
||||
return {
|
||||
"query": "erpnext.controllers.queries.get_account_list",
|
||||
"filters": [
|
||||
['Account', 'account_type', 'in', 'Bank, Cash'],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'disabled', '=', 0],
|
||||
['Account', 'company', '=', company],
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -34,4 +45,4 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for(let j=0, k=data.length-1; j<k; j++) { %}
|
||||
{% for(let j=0, k=data.length; j<k; j++) { %}
|
||||
{%
|
||||
var row = data[j];
|
||||
var row_class = data[j].parent_account ? "" : "financial-statements-important";
|
||||
|
||||
@@ -252,13 +252,6 @@ frappe.ui.form.on('Asset', {
|
||||
})
|
||||
},
|
||||
|
||||
available_for_use_date: function(frm) {
|
||||
$.each(frm.doc.finance_books || [], function(i, d) {
|
||||
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
|
||||
});
|
||||
refresh_field("finance_books");
|
||||
},
|
||||
|
||||
is_existing_asset: function(frm) {
|
||||
frm.trigger("toggle_reference_doc");
|
||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||
@@ -437,6 +430,15 @@ frappe.ui.form.on('Asset Finance Book', {
|
||||
}
|
||||
|
||||
frappe.flags.dont_change_rate = false;
|
||||
},
|
||||
|
||||
depreciation_start_date: function(frm, cdt, cdn) {
|
||||
const book = locals[cdt][cdn];
|
||||
if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
|
||||
frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
|
||||
book.depreciation_start_date = "";
|
||||
frm.refresh_field("finance_books");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -84,6 +84,11 @@ class Asset(AccountsController):
|
||||
if not self.available_for_use_date:
|
||||
frappe.throw(_("Available for use date is required"))
|
||||
|
||||
for d in self.finance_books:
|
||||
if d.depreciation_start_date == self.available_for_use_date:
|
||||
frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
|
||||
title=_("Incorrect Date"))
|
||||
|
||||
def set_missing_values(self):
|
||||
if not self.asset_category:
|
||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||
@@ -312,7 +317,7 @@ class Asset(AccountsController):
|
||||
if not row.depreciation_start_date:
|
||||
if not self.available_for_use_date:
|
||||
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
||||
row.depreciation_start_date = self.available_for_use_date
|
||||
row.depreciation_start_date = get_last_day(self.available_for_use_date)
|
||||
|
||||
if not self.is_existing_asset:
|
||||
self.opening_accumulated_depreciation = 0
|
||||
@@ -465,29 +470,37 @@ class Asset(AccountsController):
|
||||
|
||||
def validate_make_gl_entry(self):
|
||||
purchase_document = self.get_purchase_document()
|
||||
asset_bought_with_invoice = purchase_document == self.purchase_invoice
|
||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
||||
# check if expense already has been booked in case of cwip was enabled after purchasing asset
|
||||
expense_booked = False
|
||||
cwip_booked = False
|
||||
|
||||
if asset_bought_with_invoice:
|
||||
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||
(purchase_document, fixed_asset_account), as_dict=1)
|
||||
else:
|
||||
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||
(purchase_document, cwip_account), as_dict=1)
|
||||
|
||||
if cwip_enabled and (expense_booked or not cwip_booked):
|
||||
# if expense has already booked from invoice or cwip is booked from receipt
|
||||
if not purchase_document:
|
||||
return False
|
||||
elif not cwip_enabled and (not expense_booked or cwip_booked):
|
||||
# if cwip is disabled but expense hasn't been booked yet
|
||||
return True
|
||||
elif cwip_enabled:
|
||||
# default condition
|
||||
return True
|
||||
|
||||
asset_bought_with_invoice = (purchase_document == self.purchase_invoice)
|
||||
fixed_asset_account = self.get_fixed_asset_account()
|
||||
|
||||
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
||||
cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled)
|
||||
|
||||
query = """SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s"""
|
||||
if asset_bought_with_invoice:
|
||||
# with invoice purchase either expense or cwip has been booked
|
||||
expense_booked = frappe.db.sql(query, (purchase_document, fixed_asset_account), as_dict=1)
|
||||
if expense_booked:
|
||||
# if expense is already booked from invoice then do not make gl entries regardless of cwip enabled/disabled
|
||||
return False
|
||||
|
||||
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
|
||||
if cwip_booked:
|
||||
# if cwip is booked from invoice then make gl entries regardless of cwip enabled/disabled
|
||||
return True
|
||||
else:
|
||||
# with receipt purchase either cwip has been booked or no entries have been made
|
||||
if not cwip_account:
|
||||
# if cwip account isn't available do not make gl entries
|
||||
return False
|
||||
|
||||
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
|
||||
# if cwip is not booked from receipt then do not make gl entries
|
||||
# if cwip is booked from receipt then make gl entries
|
||||
return cwip_booked
|
||||
|
||||
def get_purchase_document(self):
|
||||
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
|
||||
@@ -495,20 +508,25 @@ class Asset(AccountsController):
|
||||
|
||||
return purchase_document
|
||||
|
||||
def get_asset_accounts(self):
|
||||
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
||||
asset_category = self.asset_category, company = self.company)
|
||||
def get_fixed_asset_account(self):
|
||||
return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
|
||||
|
||||
def get_cwip_account(self, cwip_enabled=False):
|
||||
cwip_account = None
|
||||
try:
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company)
|
||||
except:
|
||||
# if no cwip account found in category or company and "cwip is enabled" then raise else silently pass
|
||||
if cwip_enabled:
|
||||
raise
|
||||
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||
self.name, self.asset_category, self.company)
|
||||
|
||||
return fixed_asset_account, cwip_account
|
||||
return cwip_account
|
||||
|
||||
def make_gl_entries(self):
|
||||
gl_entries = []
|
||||
|
||||
purchase_document = self.get_purchase_document()
|
||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
|
||||
|
||||
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad
|
||||
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset
|
||||
from erpnext.assets.doctype.asset.asset import make_sales_invoice
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice
|
||||
|
||||
class TestAsset(unittest.TestCase):
|
||||
@@ -374,19 +375,18 @@ class TestAsset(unittest.TestCase):
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = nowdate()
|
||||
asset.purchase_date = nowdate()
|
||||
asset.available_for_use_date = '2020-01-01'
|
||||
asset.purchase_date = '2020-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": nowdate()
|
||||
"total_number_of_depreciations": 10,
|
||||
"frequency_of_depreciation": 1
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
|
||||
post_depreciation_entries(date=add_months(nowdate(), 10))
|
||||
post_depreciation_entries(date=add_months('2020-01-01', 4))
|
||||
|
||||
scrap_asset(asset.name)
|
||||
|
||||
@@ -395,9 +395,9 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertTrue(asset.journal_entry_for_scrap)
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
|
||||
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
|
||||
)
|
||||
|
||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
@@ -469,8 +469,7 @@ class TestAsset(unittest.TestCase):
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 10
|
||||
})
|
||||
asset.insert()
|
||||
accumulated_depreciation_after_full_schedule = \
|
||||
@@ -563,81 +562,6 @@ class TestAsset(unittest.TestCase):
|
||||
|
||||
self.assertEqual(gle, expected_gle)
|
||||
|
||||
def test_gle_with_cwip_toggling(self):
|
||||
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=5000, do_not_submit=True, location="Test Location")
|
||||
pr.set('taxes', [{
|
||||
'category': 'Total',
|
||||
'add_deduct_tax': 'Add',
|
||||
'charge_type': 'On Net Total',
|
||||
'account_head': '_Test Account Service Tax - _TC',
|
||||
'description': '_Test Account Service Tax',
|
||||
'cost_center': 'Main - _TC',
|
||||
'rate': 5.0
|
||||
}, {
|
||||
'category': 'Valuation and Total',
|
||||
'add_deduct_tax': 'Add',
|
||||
'charge_type': 'On Net Total',
|
||||
'account_head': '_Test Account Shipping Charges - _TC',
|
||||
'description': '_Test Account Shipping Charges',
|
||||
'cost_center': 'Main - _TC',
|
||||
'rate': 5.0
|
||||
}])
|
||||
pr.submit()
|
||||
expected_gle = (
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||
("CWIP Account - _TC", 5250.0, 0.0)
|
||||
)
|
||||
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Receipt' and voucher_no = %s
|
||||
order by account""", pr.name)
|
||||
self.assertEqual(pr_gle, expected_gle)
|
||||
|
||||
pi = make_invoice(pr.name)
|
||||
pi.submit()
|
||||
expected_gle = (
|
||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||
)
|
||||
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no = %s
|
||||
order by account""", pi.name)
|
||||
self.assertEqual(pi_gle, expected_gle)
|
||||
|
||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
month_end_date = get_last_day(nowdate())
|
||||
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
|
||||
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
|
||||
asset_doc.append("finance_books", {
|
||||
"expected_value_after_useful_life": 200,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": month_end_date
|
||||
})
|
||||
|
||||
# disable cwip and try submitting
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
asset_doc.submit()
|
||||
# asset should have gl entries even if cwip is disabled
|
||||
expected_gle = (
|
||||
("_Test Fixed Asset - _TC", 5250.0, 0.0),
|
||||
("CWIP Account - _TC", 0.0, 5250.0)
|
||||
)
|
||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Asset' and voucher_no = %s
|
||||
order by account""", asset_doc.name)
|
||||
self.assertEqual(gle, expected_gle)
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
|
||||
def test_expense_head(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=2, rate=200000.0, location="Test Location")
|
||||
@@ -645,6 +569,74 @@ class TestAsset(unittest.TestCase):
|
||||
doc = make_invoice(pr.name)
|
||||
|
||||
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
||||
|
||||
def test_asset_cwip_toggling_cases(self):
|
||||
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
||||
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
|
||||
cwip_acc = "CWIP Account - _TC"
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "")
|
||||
|
||||
# case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set
|
||||
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 1 -- PR with cwip disabled, Asset with cwip enabled
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 2 -- PR with cwip enabled, Asset with cwip disabled
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertTrue(gle)
|
||||
|
||||
# case 3 -- PI with cwip disabled, Asset with cwip enabled
|
||||
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 4 -- PI with cwip enabled, Asset with cwip disabled
|
||||
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertTrue(gle)
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
|
||||
|
||||
def create_asset_data():
|
||||
if not frappe.db.exists("Asset Category", "Computers"):
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint
|
||||
from frappe.utils import cint, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AssetCategory(Document):
|
||||
@@ -13,6 +13,7 @@ class AssetCategory(Document):
|
||||
self.validate_finance_books()
|
||||
self.validate_account_types()
|
||||
self.validate_account_currency()
|
||||
self.valide_cwip_account()
|
||||
|
||||
def validate_finance_books(self):
|
||||
for d in self.finance_books:
|
||||
@@ -58,6 +59,21 @@ class AssetCategory(Document):
|
||||
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
|
||||
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
|
||||
title=_("Invalid Account"))
|
||||
|
||||
def valide_cwip_account(self):
|
||||
if self.enable_cwip_accounting:
|
||||
missing_cwip_accounts_for_company = []
|
||||
for d in self.accounts:
|
||||
if (not d.capital_work_in_progress_account and
|
||||
not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")):
|
||||
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
|
||||
|
||||
if missing_cwip_accounts_for_company:
|
||||
msg = _("""To enable Capital Work in Progress Accounting, """)
|
||||
msg += _("""you must select Capital Work in Progress Account in accounts table""")
|
||||
msg += "<br><br>"
|
||||
msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company))
|
||||
frappe.throw(msg, title=_("Missing Account"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -26,4 +26,22 @@ class TestAssetCategory(unittest.TestCase):
|
||||
asset_category.insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
|
||||
def test_cwip_accounting(self):
|
||||
company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account")
|
||||
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "")
|
||||
|
||||
asset_category = frappe.new_doc("Asset Category")
|
||||
asset_category.asset_category_name = "Computers"
|
||||
asset_category.enable_cwip_accounting = 1
|
||||
|
||||
asset_category.total_number_of_depreciations = 3
|
||||
asset_category.frequency_of_depreciation = 3
|
||||
asset_category.append("accounts", {
|
||||
"company_name": "_Test Company",
|
||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||
"depreciation_expense_account": "_Test Depreciations - _TC"
|
||||
})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, asset_category.insert)
|
||||
@@ -1,347 +1,99 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-05-08 14:44:37.095570",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2018-05-08 14:44:37.095570",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"finance_book",
|
||||
"depreciation_method",
|
||||
"total_number_of_depreciations",
|
||||
"column_break_5",
|
||||
"frequency_of_depreciation",
|
||||
"depreciation_start_date",
|
||||
"expected_value_after_useful_life",
|
||||
"value_after_depreciation",
|
||||
"rate_of_depreciation"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Finance Book",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Finance Book",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "depreciation_method",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Depreciation Method",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "depreciation_method",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Depreciation Method",
|
||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "total_number_of_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Number of Depreciations",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "total_number_of_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Number of Depreciations",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "frequency_of_depreciation",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Frequency of Depreciation (Months)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "frequency_of_depreciation",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Frequency of Depreciation (Months)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "depreciation_start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Depreciation Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||
"fieldname": "depreciation_start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Depreciation Posting Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "expected_value_after_useful_life",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expected Value After Useful Life",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||
"fieldname": "expected_value_after_useful_life",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Expected Value After Useful Life",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "value_after_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Value After Depreciation",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "value_after_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Value After Depreciation",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
||||
"description": "In Percentage",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "rate_of_depreciation",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rate of Depreciation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
||||
"description": "In Percentage",
|
||||
"fieldname": "rate_of_depreciation",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Rate of Depreciation"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-04-09 19:45:14.523488",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-16 12:11:30.631788",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -32,8 +32,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 10
|
||||
})
|
||||
|
||||
if asset.docstatus == 0:
|
||||
@@ -82,8 +81,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 10
|
||||
})
|
||||
if asset.docstatus == 0:
|
||||
asset.submit()
|
||||
|
||||
@@ -202,6 +202,91 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_update_child_with_tax_template(self):
|
||||
"""
|
||||
Test Action: Create a PO with one item having its tax account head already in the PO.
|
||||
Add the same item + new item with tax template via Update Items.
|
||||
Expected result: First Item's tax row is updated. New tax row is added for second Item.
|
||||
"""
|
||||
if not frappe.db.exists("Item", "Test Item with Tax"):
|
||||
make_item("Test Item with Tax", {
|
||||
'is_stock_item': 1,
|
||||
})
|
||||
|
||||
if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Item Tax Template',
|
||||
'title': 'Test Update Items Template',
|
||||
'company': '_Test Company',
|
||||
'taxes': [
|
||||
{
|
||||
'tax_type': "_Test Account Service Tax - _TC",
|
||||
'tax_rate': 10,
|
||||
}
|
||||
]
|
||||
}).insert()
|
||||
|
||||
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
|
||||
|
||||
new_item_with_tax.append("taxes", {
|
||||
"item_tax_template": "Test Update Items Template",
|
||||
"valid_from": nowdate()
|
||||
})
|
||||
new_item_with_tax.save()
|
||||
|
||||
tax_template = "_Test Account Excise Duty @ 10"
|
||||
item = "_Test Item Home Desktop 100"
|
||||
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
||||
item_doc = frappe.get_doc("Item", item)
|
||||
item_doc.append("taxes", {
|
||||
"item_tax_template": tax_template,
|
||||
"valid_from": nowdate()
|
||||
})
|
||||
item_doc.save()
|
||||
else:
|
||||
# update valid from
|
||||
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
|
||||
where parent = %(item)s and item_tax_template = %(tax)s""",
|
||||
{"item": item, "tax": tax_template})
|
||||
|
||||
po = create_purchase_order(item_code=item, qty=1, do_not_save=1)
|
||||
|
||||
po.append("taxes", {
|
||||
"account_head": "_Test Account Excise Duty - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Excise Duty",
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"rate": 10
|
||||
})
|
||||
po.insert()
|
||||
po.submit()
|
||||
|
||||
self.assertEqual(po.taxes[0].tax_amount, 50)
|
||||
self.assertEqual(po.taxes[0].total, 550)
|
||||
|
||||
items = json.dumps([
|
||||
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
|
||||
{'item_code' : item, 'rate' : 100, 'qty' : 1}, # added item whose tax account head already exists in PO
|
||||
{'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO
|
||||
])
|
||||
update_child_qty_rate('Purchase Order', items, po.name)
|
||||
|
||||
po.reload()
|
||||
self.assertEqual(po.taxes[0].tax_amount, 70)
|
||||
self.assertEqual(po.taxes[0].total, 770)
|
||||
self.assertEqual(po.taxes[1].account_head, "_Test Account Service Tax - _TC")
|
||||
self.assertEqual(po.taxes[1].tax_amount, 70)
|
||||
self.assertEqual(po.taxes[1].total, 840)
|
||||
|
||||
# teardown
|
||||
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
|
||||
where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template})
|
||||
po.cancel()
|
||||
po.delete()
|
||||
new_item_with_tax.delete()
|
||||
frappe.get_doc("Item Tax Template", "Test Update Items Template").delete()
|
||||
|
||||
def test_update_qty(self):
|
||||
po = create_purchase_order()
|
||||
|
||||
@@ -733,6 +818,59 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
|
||||
update_backflush_based_on("BOM")
|
||||
|
||||
def test_supplied_qty_against_subcontracted_po(self):
|
||||
item_code = "_Test Subcontracted FG Item 5"
|
||||
make_item('Sub Contracted Raw Material 4', {
|
||||
'is_stock_item': 1,
|
||||
'is_sub_contracted_item': 1
|
||||
})
|
||||
|
||||
make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"])
|
||||
|
||||
update_backflush_based_on("Material Transferred for Subcontract")
|
||||
|
||||
order_qty = 250
|
||||
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", do_not_save=True)
|
||||
|
||||
# Add same subcontracted items multiple times
|
||||
po.append("items", {
|
||||
"item_code": item_code,
|
||||
"qty": order_qty,
|
||||
"schedule_date": add_days(nowdate(), 1),
|
||||
"warehouse": "_Test Warehouse - _TC"
|
||||
})
|
||||
|
||||
po.set_missing_values()
|
||||
po.submit()
|
||||
|
||||
# Material receipt entry for the raw materials which will be send to supplier
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Sub Contracted Raw Material 4", qty=500, basic_rate=100)
|
||||
|
||||
rm_items = [
|
||||
{
|
||||
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
|
||||
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name
|
||||
},
|
||||
{
|
||||
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
|
||||
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[1].name
|
||||
},
|
||||
]
|
||||
|
||||
# Raw Materials transfer entry from stores to supplier's warehouse
|
||||
rm_item_string = json.dumps(rm_items)
|
||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||
se.submit()
|
||||
|
||||
po_doc = frappe.get_doc("Purchase Order", po.name)
|
||||
for row in po_doc.supplied_items:
|
||||
# Valid that whether transferred quantity is matching with supplied qty or not in the purchase order
|
||||
self.assertEqual(row.supplied_qty, 250.0)
|
||||
|
||||
update_backflush_based_on("BOM")
|
||||
|
||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
||||
|
||||
@@ -143,7 +143,7 @@ def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company'))
|
||||
conditions += " AND parent.company=%s" % frappe.db.escape(filters.get('company'))
|
||||
|
||||
if filters.get("cost_center") or filters.get("project"):
|
||||
conditions += """
|
||||
@@ -151,10 +151,10 @@ def get_conditions(filters):
|
||||
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " AND par.transaction_date>='%s'" % filters.get('from_date')
|
||||
conditions += " AND parent.transaction_date>='%s'" % filters.get('from_date')
|
||||
|
||||
if filters.get("to_date"):
|
||||
conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
|
||||
conditions += " AND parent.transaction_date<='%s'" % filters.get('to_date')
|
||||
return conditions
|
||||
|
||||
def get_data(filters):
|
||||
@@ -198,21 +198,23 @@ def get_mapped_mr_details(conditions):
|
||||
mr_records = {}
|
||||
mr_details = frappe.db.sql("""
|
||||
SELECT
|
||||
par.transaction_date,
|
||||
par.per_ordered,
|
||||
par.owner,
|
||||
parent.transaction_date,
|
||||
parent.per_ordered,
|
||||
parent.owner,
|
||||
child.name,
|
||||
child.parent,
|
||||
child.amount,
|
||||
child.qty,
|
||||
child.item_code,
|
||||
child.uom,
|
||||
par.status
|
||||
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
|
||||
parent.status,
|
||||
child.project,
|
||||
child.cost_center
|
||||
FROM `tabMaterial Request` parent, `tabMaterial Request Item` child
|
||||
WHERE
|
||||
par.per_ordered>=0
|
||||
AND par.name=child.parent
|
||||
AND par.docstatus=1
|
||||
parent.per_ordered>=0
|
||||
AND parent.name=child.parent
|
||||
AND parent.docstatus=1
|
||||
{conditions}
|
||||
""".format(conditions=conditions), as_dict=1) #nosec
|
||||
|
||||
@@ -232,7 +234,9 @@ def get_mapped_mr_details(conditions):
|
||||
status=record.status,
|
||||
actual_cost=0,
|
||||
purchase_order_amt=0,
|
||||
purchase_order_amt_in_company_currency=0
|
||||
purchase_order_amt_in_company_currency=0,
|
||||
project = record.project,
|
||||
cost_center = record.cost_center
|
||||
)
|
||||
procurement_record_against_mr.append(procurement_record_details)
|
||||
return mr_records, procurement_record_against_mr
|
||||
@@ -280,16 +284,16 @@ def get_po_entries(conditions):
|
||||
child.amount,
|
||||
child.base_amount,
|
||||
child.schedule_date,
|
||||
par.transaction_date,
|
||||
par.supplier,
|
||||
par.status,
|
||||
par.owner
|
||||
FROM `tabPurchase Order` par, `tabPurchase Order Item` child
|
||||
parent.transaction_date,
|
||||
parent.supplier,
|
||||
parent.status,
|
||||
parent.owner
|
||||
FROM `tabPurchase Order` parent, `tabPurchase Order Item` child
|
||||
WHERE
|
||||
par.docstatus = 1
|
||||
AND par.name = child.parent
|
||||
AND par.status not in ("Closed","Completed","Cancelled")
|
||||
parent.docstatus = 1
|
||||
AND parent.name = child.parent
|
||||
AND parent.status not in ("Closed","Completed","Cancelled")
|
||||
{conditions}
|
||||
GROUP BY
|
||||
par.name, child.item_code
|
||||
parent.name, child.item_code
|
||||
""".format(conditions=conditions), as_dict=1) #nosec
|
||||
51
erpnext/change_log/v12/v12_13_0.md
Normal file
51
erpnext/change_log/v12/v12_13_0.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## ERPNext v12.13.0 Release Note
|
||||
|
||||
### Fixes and Enhancements
|
||||
|
||||
- Warehouse address filtered based on warehouse ([#23381](https://github.com/frappe/erpnext/pull/23381))
|
||||
- COGS validation in the purchase receipt ([#23536](https://github.com/frappe/erpnext/pull/23536))
|
||||
- Item Tax Updating via `Update Items` in SO/PO ([#23338](https://github.com/frappe/erpnext/pull/23338))
|
||||
- Pricing rule selector is wrong ([#22328](https://github.com/frappe/erpnext/pull/22328))
|
||||
- Adding filters validation Batch-Wise Balance History ([#23396](https://github.com/frappe/erpnext/pull/23396))
|
||||
- Add company and correct filter in bank statement reconciliation report filters ([#23618](https://github.com/frappe/erpnext/pull/23618))
|
||||
- Incorrect consumed qty if raw material with batch ([#23389](https://github.com/frappe/erpnext/pull/23389))
|
||||
- Balance serial nos in stock ledger report ([#23520](https://github.com/frappe/erpnext/pull/23520))
|
||||
- Use Plaid's new API (v12) ([#23317](https://github.com/frappe/erpnext/pull/23317))
|
||||
- Cost Center filter in accounts receivable and payable report ([#23356](https://github.com/frappe/erpnext/pull/23356))
|
||||
- Incorrect operation time calculation for batch size ([#23480](https://github.com/frappe/erpnext/pull/23480))
|
||||
- Serial no field is blank in stock reconciliation ([#23648](https://github.com/frappe/erpnext/pull/23648))
|
||||
- Manually set serial nos override with current available serial nos ([#23657](https://github.com/frappe/erpnext/pull/23657))
|
||||
- Can't save item price after adding child table ([#23594](https://github.com/frappe/erpnext/pull/23594))
|
||||
- Naming series - cannot reset current value to zero ([#23505](https://github.com/frappe/erpnext/pull/23505))
|
||||
- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23463](https://github.com/frappe/erpnext/pull/23463))
|
||||
- Taxes not getting fetched from Opportunity to Quotation ([#23354](https://github.com/frappe/erpnext/pull/23354))
|
||||
- Stock reconciliation, incorrect serial nos fetched in the current serial no field ([#23366](https://github.com/frappe/erpnext/pull/23366))
|
||||
- Cannot create asset if cwip disabled and account not set ([#23584](https://github.com/frappe/erpnext/pull/23584))
|
||||
- Leave application status fix ([#23411](https://github.com/frappe/erpnext/pull/23411))
|
||||
- Not able to do overproduction ([#23600](https://github.com/frappe/erpnext/pull/23600))
|
||||
- Incorrect supplied qty error ([#23619](https://github.com/frappe/erpnext/pull/23619))
|
||||
- Download Required Materials not working for production plan ([#23403](https://github.com/frappe/erpnext/pull/23403))
|
||||
- Make account number length configurable ([#23496](https://github.com/frappe/erpnext/pull/23496))
|
||||
- Include item_code in items result to allow adding product info in custom templates ([#23440](https://github.com/frappe/erpnext/pull/23440))
|
||||
- Mode of payment getting overwritten by default mode of payment for returns ([#23599](https://github.com/frappe/erpnext/pull/23599))
|
||||
- Show total row in print format of financial statement ([#23565](https://github.com/frappe/erpnext/pull/23565))
|
||||
- Performance issue while adding template item in the cart ([#23508](https://github.com/frappe/erpnext/pull/23508))
|
||||
- Display item name instead of item code in offline POS ([#23451](https://github.com/frappe/erpnext/pull/23451))
|
||||
- Set stock UOM in item while creating material request from stock entry ([#23430](https://github.com/frappe/erpnext/pull/23430))
|
||||
- Book loss amount in the COGS instead of stock received but not billed ([#23412](https://github.com/frappe/erpnext/pull/23412))
|
||||
- Depreciation start date ux fixes ([#23340](https://github.com/frappe/erpnext/pull/23340))
|
||||
- Payment Schedule not fetching ([#23477](https://github.com/frappe/erpnext/pull/23477))
|
||||
- Taxable value in GSTR 3B report ([#23552](https://github.com/frappe/erpnext/pull/23552))
|
||||
- Negative stock error while submitting stock reco for batch item ([#23564](https://github.com/frappe/erpnext/pull/23564))
|
||||
- Check only Read and Write Permission in Update Items ([#23458](https://github.com/frappe/erpnext/pull/23458))
|
||||
- Handle Blank from/to range in Numeric Item Attribute ([#23484](https://github.com/frappe/erpnext/pull/23484))
|
||||
- Added new filters in the Batch-wise balance history report ([#23522](https://github.com/frappe/erpnext/pull/23522))
|
||||
- Added filter show in website for filtering product ([#23638](https://github.com/frappe/erpnext/pull/23638))
|
||||
- Enabled no copy property for Supplier Invoice Date to avoid due date validation ([#23367](https://github.com/frappe/erpnext/pull/23367))
|
||||
- Showing a negative balance in expired leaves ([#23426](https://github.com/frappe/erpnext/pull/23426))
|
||||
- Validate if removed item attributes exist in variants ([#23591](https://github.com/frappe/erpnext/pull/23591))
|
||||
- Longer timeout for company replace abbreviation ([#23442](https://github.com/frappe/erpnext/pull/23442))
|
||||
- Online pos print not working ([#23377](https://github.com/frappe/erpnext/pull/23377))
|
||||
- Last purchase rate in item prices report ([#23507](https://github.com/frappe/erpnext/pull/23507))
|
||||
- Do not consider opening entries for TDS calculation ([#23598](https://github.com/frappe/erpnext/pull/23598))
|
||||
- Project value is missing from procurement-tracker ([#23551](https://github.com/frappe/erpnext/pull/23551))
|
||||
@@ -20,7 +20,7 @@ from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_t
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
from six import text_type
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||
from erpnext.stock.get_item_details import get_item_warehouse
|
||||
from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
|
||||
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
|
||||
@@ -1127,6 +1127,43 @@ def get_supplier_block_status(party_name):
|
||||
}
|
||||
return info
|
||||
|
||||
def set_child_tax_template_and_map(item, child_item, parent_doc):
|
||||
args = {
|
||||
'item_code': item.item_code,
|
||||
'posting_date': parent_doc.transaction_date,
|
||||
'tax_category': parent_doc.get('tax_category'),
|
||||
'company': parent_doc.get('company')
|
||||
}
|
||||
|
||||
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
|
||||
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)
|
||||
|
||||
def add_taxes_from_tax_template(child_item, parent_doc):
|
||||
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:
|
||||
tax_map = json.loads(child_item.get("item_tax_rate"))
|
||||
for tax_type in tax_map:
|
||||
tax_rate = flt(tax_map[tax_type])
|
||||
taxes = parent_doc.get('taxes') or []
|
||||
# add new row for tax head only if missing
|
||||
found = any(tax.account_head == tax_type for tax in taxes)
|
||||
if not found:
|
||||
tax_row = parent_doc.append("taxes", {})
|
||||
tax_row.update({
|
||||
"description" : str(tax_type).split(' - ')[0],
|
||||
"charge_type" : "On Net Total",
|
||||
"account_head" : tax_type,
|
||||
"rate" : tax_rate
|
||||
})
|
||||
if parent_doc.doctype == "Purchase Order":
|
||||
tax_row.update({
|
||||
"category" : "Total",
|
||||
"add_deduct_tax" : "Add"
|
||||
})
|
||||
tax_row.db_insert()
|
||||
|
||||
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
|
||||
"""
|
||||
Returns a Sales Order Item child item containing the default values
|
||||
@@ -1140,6 +1177,8 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
|
||||
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
|
||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
|
||||
child_item.uom = item.stock_uom
|
||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||
add_taxes_from_tax_template(child_item, p_doc)
|
||||
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
||||
if not child_item.warehouse:
|
||||
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
||||
@@ -1162,6 +1201,8 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
|
||||
child_item.uom = item.stock_uom
|
||||
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
||||
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||
add_taxes_from_tax_template(child_item, p_doc)
|
||||
return child_item
|
||||
|
||||
def validate_and_delete_children(parent, data):
|
||||
@@ -1270,19 +1311,21 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
validate_quantity(child_item, d)
|
||||
|
||||
child_item.qty = flt(d.get("qty"))
|
||||
precision = child_item.precision("rate") or 2
|
||||
rate_precision = child_item.precision("rate") or 2
|
||||
conv_fac_precision = child_item.precision("conversion_factor") or 2
|
||||
qty_precision = child_item.precision("qty") or 2
|
||||
|
||||
if flt(child_item.billed_amt, precision) > flt(flt(d.get("rate")) * flt(d.get("qty")), precision):
|
||||
if flt(child_item.billed_amt, rate_precision) > flt(flt(d.get("rate"), rate_precision) * flt(d.get("qty"), qty_precision), rate_precision):
|
||||
frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
|
||||
.format(child_item.idx, child_item.item_code))
|
||||
else:
|
||||
child_item.rate = flt(d.get("rate"))
|
||||
child_item.rate = flt(d.get("rate"), rate_precision)
|
||||
|
||||
if d.get("conversion_factor"):
|
||||
if child_item.stock_uom == child_item.uom:
|
||||
child_item.conversion_factor = 1
|
||||
else:
|
||||
child_item.conversion_factor = flt(d.get('conversion_factor'))
|
||||
child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision)
|
||||
|
||||
if d.get("delivery_date") and parent_doctype == 'Sales Order':
|
||||
child_item.delivery_date = d.get('delivery_date')
|
||||
|
||||
@@ -10,6 +10,7 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.stock.doctype.item.item import set_item_default
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
|
||||
@@ -53,10 +54,10 @@ class SellingController(StockController):
|
||||
super(SellingController, self).set_missing_values(for_validate)
|
||||
|
||||
# set contact and address details for customer, if they are not mentioned
|
||||
self.set_missing_lead_customer_details()
|
||||
self.set_missing_lead_customer_details(for_validate=for_validate)
|
||||
self.set_price_list_and_item_details(for_validate=for_validate)
|
||||
|
||||
def set_missing_lead_customer_details(self):
|
||||
def set_missing_lead_customer_details(self, for_validate=False):
|
||||
customer, lead = None, None
|
||||
if getattr(self, "customer", None):
|
||||
customer = self.customer
|
||||
@@ -93,6 +94,11 @@ class SellingController(StockController):
|
||||
posting_date=self.get('transaction_date') or self.get('posting_date'),
|
||||
company=self.company))
|
||||
|
||||
if self.get('taxes_and_charges') and not self.get('taxes') and not for_validate:
|
||||
taxes = get_taxes_and_charges('Sales Taxes and Charges Template', self.taxes_and_charges)
|
||||
for tax in taxes:
|
||||
self.append('taxes', tax)
|
||||
|
||||
def set_price_list_and_item_details(self, for_validate=False):
|
||||
self.set_price_list_currency("Selling")
|
||||
self.set_missing_item_details(for_validate=for_validate)
|
||||
|
||||
@@ -629,22 +629,29 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
|
||||
|
||||
def update_paid_amount_for_return(self, total_amount_to_pay):
|
||||
default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment',
|
||||
{'parent': self.doc.pos_profile, 'default': 1},
|
||||
['mode_of_payment', 'type', 'account'], as_dict=1)
|
||||
existing_amount = 0
|
||||
|
||||
self.doc.payments = []
|
||||
for payment in self.doc.payments:
|
||||
existing_amount += payment.amount
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.append('payments', {
|
||||
'mode_of_payment': default_mode_of_payment.mode_of_payment,
|
||||
'type': default_mode_of_payment.type,
|
||||
'account': default_mode_of_payment.account,
|
||||
'amount': total_amount_to_pay
|
||||
})
|
||||
else:
|
||||
self.doc.is_pos = 0
|
||||
self.doc.pos_profile = ''
|
||||
# do not override user entered amount if equal to total_amount_to_pay
|
||||
if existing_amount != total_amount_to_pay:
|
||||
default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment',
|
||||
{'parent': self.doc.pos_profile, 'default': 1},
|
||||
['mode_of_payment', 'type', 'account'], as_dict=1)
|
||||
|
||||
self.doc.payments = []
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.append('payments', {
|
||||
'mode_of_payment': default_mode_of_payment.mode_of_payment,
|
||||
'type': default_mode_of_payment.type,
|
||||
'account': default_mode_of_payment.account,
|
||||
'amount': total_amount_to_pay
|
||||
})
|
||||
else:
|
||||
self.doc.is_pos = 0
|
||||
self.doc.pos_profile = ''
|
||||
|
||||
self.calculate_paid_amount()
|
||||
|
||||
|
||||
@@ -2,81 +2,90 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
from frappe.utils.password import get_decrypted_password
|
||||
from plaid import Client
|
||||
from plaid.errors import APIError, ItemError
|
||||
import plaid
|
||||
import requests
|
||||
from plaid.errors import APIError, ItemError, InvalidRequestError
|
||||
|
||||
import frappe
|
||||
import requests
|
||||
from frappe import _
|
||||
|
||||
|
||||
class PlaidConnector():
|
||||
def __init__(self, access_token=None):
|
||||
|
||||
plaid_settings = frappe.get_single("Plaid Settings")
|
||||
|
||||
self.config = {
|
||||
"plaid_client_id": plaid_settings.plaid_client_id,
|
||||
"plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
|
||||
"plaid_public_key": plaid_settings.plaid_public_key,
|
||||
"plaid_env": plaid_settings.plaid_env
|
||||
}
|
||||
|
||||
self.client = Client(client_id=self.config.get("plaid_client_id"),
|
||||
secret=self.config.get("plaid_secret"),
|
||||
public_key=self.config.get("plaid_public_key"),
|
||||
environment=self.config.get("plaid_env")
|
||||
)
|
||||
|
||||
self.access_token = access_token
|
||||
self.settings = frappe.get_single("Plaid Settings")
|
||||
self.products = ["auth", "transactions"]
|
||||
self.client_name = frappe.local.site
|
||||
self.client = plaid.Client(
|
||||
client_id=self.settings.plaid_client_id,
|
||||
secret=self.settings.get_password("plaid_secret"),
|
||||
environment=self.settings.plaid_env,
|
||||
api_version="2019-05-29"
|
||||
)
|
||||
|
||||
def get_access_token(self, public_token):
|
||||
if public_token is None:
|
||||
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
|
||||
|
||||
response = self.client.Item.public_token.exchange(public_token)
|
||||
access_token = response['access_token']
|
||||
|
||||
access_token = response["access_token"]
|
||||
return access_token
|
||||
|
||||
def get_link_token(self):
|
||||
token_request = {
|
||||
"client_name": self.client_name,
|
||||
"client_id": self.settings.plaid_client_id,
|
||||
"secret": self.settings.plaid_secret,
|
||||
"products": self.products,
|
||||
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
||||
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
||||
"country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
|
||||
"user": {
|
||||
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.client.LinkToken.create(token_request)
|
||||
except InvalidRequestError:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid invalid request error"))
|
||||
frappe.msgprint(_("Please check your Plaid client ID and secret values"))
|
||||
except APIError as e:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
|
||||
frappe.throw(_(str(e)), title=_("Authentication Failed"))
|
||||
else:
|
||||
return response["link_token"]
|
||||
|
||||
def auth(self):
|
||||
try:
|
||||
self.client.Auth.get(self.access_token)
|
||||
print("Authentication successful.....")
|
||||
except ItemError as e:
|
||||
if e.code == 'ITEM_LOGIN_REQUIRED':
|
||||
pass
|
||||
else:
|
||||
if e.code == "ITEM_LOGIN_REQUIRED":
|
||||
pass
|
||||
except APIError as e:
|
||||
if e.code == 'PLANNED_MAINTENANCE':
|
||||
pass
|
||||
else:
|
||||
if e.code == "PLANNED_MAINTENANCE":
|
||||
pass
|
||||
except requests.Timeout:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(e)
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
|
||||
frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'})
|
||||
frappe.throw(_(str(e)), title=_("Authentication Failed"))
|
||||
|
||||
def get_transactions(self, start_date, end_date, account_id=None):
|
||||
self.auth()
|
||||
kwargs = dict(
|
||||
access_token=self.access_token,
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
if account_id:
|
||||
kwargs.update(dict(account_ids=[account_id]))
|
||||
|
||||
try:
|
||||
self.auth()
|
||||
if account_id:
|
||||
account_ids = [account_id]
|
||||
|
||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
|
||||
|
||||
else:
|
||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)
|
||||
|
||||
transactions = response['transactions']
|
||||
|
||||
while len(transactions) < response['total_transactions']:
|
||||
response = self.client.Transactions.get(**kwargs)
|
||||
transactions = response["transactions"]
|
||||
while len(transactions) < response["total_transactions"]:
|
||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
|
||||
transactions.extend(response['transactions'])
|
||||
transactions.extend(response["transactions"])
|
||||
return transactions
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
frappe.provide("erpnext.integrations");
|
||||
|
||||
frappe.ui.form.on('Plaid Settings', {
|
||||
enabled: function(frm) {
|
||||
enabled: function (frm) {
|
||||
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
|
||||
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
|
||||
frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
|
||||
frm.toggle_reqd('plaid_env', frm.doc.enabled);
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.enabled) {
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.enabled) {
|
||||
frm.add_custom_button('Link a new bank account', () => {
|
||||
new erpnext.integrations.plaidLink(frm);
|
||||
});
|
||||
@@ -22,17 +22,16 @@ frappe.ui.form.on('Plaid Settings', {
|
||||
erpnext.integrations.plaidLink = class plaidLink {
|
||||
constructor(parent) {
|
||||
this.frm = parent;
|
||||
this.product = ["transactions", "auth"];
|
||||
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
||||
this.init_config();
|
||||
}
|
||||
|
||||
init_config() {
|
||||
const me = this;
|
||||
me.plaid_env = me.frm.doc.plaid_env;
|
||||
me.plaid_public_key = me.frm.doc.plaid_public_key;
|
||||
me.client_name = frappe.boot.sitename;
|
||||
me.init_plaid();
|
||||
async init_config() {
|
||||
this.product = ["auth", "transactions"];
|
||||
this.plaid_env = this.frm.doc.plaid_env;
|
||||
this.client_name = frappe.boot.sitename;
|
||||
this.token = await this.frm.call("get_link_token").then(resp => resp.message);
|
||||
this.init_plaid();
|
||||
}
|
||||
|
||||
init_plaid() {
|
||||
@@ -69,17 +68,17 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
}
|
||||
|
||||
onScriptLoaded(me) {
|
||||
me.linkHandler = window.Plaid.create({
|
||||
me.linkHandler = Plaid.create({
|
||||
clientName: me.client_name,
|
||||
product: me.product,
|
||||
env: me.plaid_env,
|
||||
key: me.plaid_public_key,
|
||||
onSuccess: me.plaid_success,
|
||||
product: me.product
|
||||
token: me.token,
|
||||
onSuccess: me.plaid_success
|
||||
});
|
||||
}
|
||||
|
||||
onScriptError(error) {
|
||||
frappe.msgprint('There was an issue loading the link-initialize.js script');
|
||||
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
|
||||
frappe.msgprint(error);
|
||||
}
|
||||
|
||||
@@ -87,21 +86,25 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
const me = this;
|
||||
|
||||
frappe.prompt({
|
||||
fieldtype:"Link",
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
label:__("Company"),
|
||||
fieldname:"company",
|
||||
reqd:1
|
||||
label: __("Company"),
|
||||
fieldname: "company",
|
||||
reqd: 1
|
||||
}, (data) => {
|
||||
me.company = data.company;
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
|
||||
.then((result) => {
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
|
||||
bank: result, company: me.company});
|
||||
})
|
||||
.then(() => {
|
||||
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
|
||||
token: token,
|
||||
response: response
|
||||
}).then((result) => {
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
|
||||
response: response,
|
||||
bank: result,
|
||||
company: me.company
|
||||
});
|
||||
}).then(() => {
|
||||
frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
|
||||
});
|
||||
}, __("Select a company"), __("Continue"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2018-10-25 10:02:48.656165",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@@ -12,7 +11,6 @@
|
||||
"plaid_client_id",
|
||||
"plaid_secret",
|
||||
"column_break_7",
|
||||
"plaid_public_key",
|
||||
"plaid_env"
|
||||
],
|
||||
"fields": [
|
||||
@@ -41,12 +39,6 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Plaid Secret"
|
||||
},
|
||||
{
|
||||
"fieldname": "plaid_public_key",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Plaid Public Key"
|
||||
},
|
||||
{
|
||||
"fieldname": "plaid_env",
|
||||
"fieldtype": "Select",
|
||||
@@ -69,8 +61,7 @@
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-07 15:21:11.616231",
|
||||
"modified": "2020-09-12 02:31:44.542385",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Plaid Settings",
|
||||
|
||||
@@ -2,30 +2,36 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
import frappe
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
|
||||
from frappe.utils import getdate, formatdate, today, add_months
|
||||
from frappe import _
|
||||
from frappe.desk.doctype.tag.tag import add_tag
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_months, formatdate, getdate, today
|
||||
|
||||
|
||||
class PlaidSettings(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def get_link_token():
|
||||
plaid = PlaidConnector()
|
||||
return plaid.get_link_token()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def plaid_configuration():
|
||||
def get_plaid_configuration():
|
||||
if frappe.db.get_single_value("Plaid Settings", "enabled"):
|
||||
plaid_settings = frappe.get_single("Plaid Settings")
|
||||
return {
|
||||
"plaid_public_key": plaid_settings.plaid_public_key,
|
||||
"plaid_env": plaid_settings.plaid_env,
|
||||
"link_token": plaid_settings.get_link_token(),
|
||||
"client_name": frappe.local.site
|
||||
}
|
||||
else:
|
||||
return "disabled"
|
||||
|
||||
return "disabled"
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_institution(token, response):
|
||||
@@ -33,6 +39,7 @@ def add_institution(token, response):
|
||||
|
||||
plaid = PlaidConnector()
|
||||
access_token = plaid.get_access_token(token)
|
||||
bank = None
|
||||
|
||||
if not frappe.db.exists("Bank", response["institution"]["name"]):
|
||||
try:
|
||||
@@ -44,7 +51,6 @@ def add_institution(token, response):
|
||||
bank.insert()
|
||||
except Exception:
|
||||
frappe.throw(frappe.get_traceback())
|
||||
|
||||
else:
|
||||
bank = frappe.get_doc("Bank", response["institution"]["name"])
|
||||
bank.plaid_access_token = access_token
|
||||
@@ -52,6 +58,7 @@ def add_institution(token, response):
|
||||
|
||||
return bank
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_bank_accounts(response, bank, company):
|
||||
try:
|
||||
@@ -92,9 +99,8 @@ def add_bank_accounts(response, bank, company):
|
||||
new_account.insert()
|
||||
|
||||
result.append(new_account.name)
|
||||
|
||||
except frappe.UniqueValidationError:
|
||||
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name))
|
||||
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
|
||||
except Exception:
|
||||
frappe.throw(frappe.get_traceback())
|
||||
|
||||
@@ -103,6 +109,7 @@ def add_bank_accounts(response, bank, company):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def add_account_type(account_type):
|
||||
try:
|
||||
frappe.get_doc({
|
||||
@@ -122,10 +129,11 @@ def add_account_subtype(account_subtype):
|
||||
except Exception:
|
||||
frappe.throw(frappe.get_traceback())
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def sync_transactions(bank, bank_account):
|
||||
''' Sync transactions based on the last integration date as the start date, after sync is completed
|
||||
add the transaction date of the oldest transaction as the last integration date '''
|
||||
"""Sync transactions based on the last integration date as the start date, after sync is completed
|
||||
add the transaction date of the oldest transaction as the last integration date."""
|
||||
|
||||
last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
|
||||
if last_transaction_date:
|
||||
@@ -148,10 +156,10 @@ def sync_transactions(bank, bank_account):
|
||||
len(result), bank_account, start_date, end_date))
|
||||
|
||||
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
|
||||
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
||||
|
||||
|
||||
def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||
access_token = None
|
||||
|
||||
@@ -169,6 +177,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||
|
||||
return transactions
|
||||
|
||||
|
||||
def new_bank_transaction(transaction):
|
||||
result = []
|
||||
|
||||
@@ -183,8 +192,8 @@ def new_bank_transaction(transaction):
|
||||
|
||||
status = "Pending" if transaction["pending"] == "True" else "Settled"
|
||||
|
||||
tags = []
|
||||
try:
|
||||
tags = []
|
||||
tags += transaction["category"]
|
||||
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
|
||||
except KeyError:
|
||||
@@ -217,6 +226,7 @@ def new_bank_transaction(transaction):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def automatic_synchronization():
|
||||
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
||||
|
||||
@@ -224,4 +234,8 @@ def automatic_synchronization():
|
||||
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
|
||||
|
||||
for plaid_account in plaid_accounts:
|
||||
frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name)
|
||||
frappe.enqueue(
|
||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
||||
bank=plaid_account.bank,
|
||||
bank_account=plaid_account.name
|
||||
)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import plaid_configuration, add_account_type, add_account_subtype, new_bank_transaction, add_bank_accounts
|
||||
import json
|
||||
from frappe.utils.response import json_handler
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import (
|
||||
add_account_subtype, add_account_type, add_bank_accounts,
|
||||
new_bank_transaction, get_plaid_configuration)
|
||||
from frappe.utils.response import json_handler
|
||||
|
||||
|
||||
class TestPlaidSettings(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -31,7 +34,7 @@ class TestPlaidSettings(unittest.TestCase):
|
||||
|
||||
def test_plaid_disabled(self):
|
||||
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
|
||||
self.assertTrue(plaid_configuration() == "disabled")
|
||||
self.assertTrue(get_plaid_configuration() == "disabled")
|
||||
|
||||
def test_add_account_type(self):
|
||||
add_account_type("brokerage")
|
||||
@@ -64,7 +67,7 @@ class TestPlaidSettings(unittest.TestCase):
|
||||
'mask': '0000',
|
||||
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
|
||||
'name': 'Plaid Checking'
|
||||
}],
|
||||
}],
|
||||
'institution': {
|
||||
'institution_id': 'ins_6',
|
||||
'name': 'Citi'
|
||||
@@ -100,7 +103,7 @@ class TestPlaidSettings(unittest.TestCase):
|
||||
'mask': '0000',
|
||||
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
|
||||
'name': 'Plaid Checking'
|
||||
}],
|
||||
}],
|
||||
'institution': {
|
||||
'institution_id': 'ins_6',
|
||||
'name': 'Citi'
|
||||
@@ -152,4 +155,4 @@ class TestPlaidSettings(unittest.TestCase):
|
||||
|
||||
new_bank_transaction(transactions)
|
||||
|
||||
self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)
|
||||
self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
@@ -31,7 +32,7 @@ class HolidayList(Document):
|
||||
|
||||
|
||||
def validate_days(self):
|
||||
if self.from_date > self.to_date:
|
||||
if getdate(self.from_date) > getdate(self.to_date):
|
||||
throw(_("To Date cannot be before From Date"))
|
||||
|
||||
for day in self.get("holidays"):
|
||||
|
||||
@@ -244,9 +244,10 @@
|
||||
],
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"max_attachments": 3,
|
||||
"modified": "2020-09-23 18:53:11.608446",
|
||||
"modified": "2020-09-23 19:11:58.806837",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Application",
|
||||
|
||||
@@ -251,6 +251,10 @@ def make_bom(**args):
|
||||
'rate': item_doc.valuation_rate or args.rate,
|
||||
})
|
||||
|
||||
bom.insert(ignore_permissions=True)
|
||||
bom.submit()
|
||||
if not args.do_not_save:
|
||||
bom.insert(ignore_permissions=True)
|
||||
|
||||
if not args.do_not_submit:
|
||||
bom.submit()
|
||||
|
||||
return bom
|
||||
|
||||
@@ -371,6 +371,49 @@ class TestWorkOrder(unittest.TestCase):
|
||||
ste1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1))
|
||||
self.assertEqual(len(ste1.items), 3)
|
||||
|
||||
def test_operation_time_with_batch_size(self):
|
||||
fg_item = "Test Batch Size Item For BOM"
|
||||
rm1 = "Test Batch Size Item RM 1 For BOM"
|
||||
|
||||
for item in ["Test Batch Size Item For BOM", "Test Batch Size Item RM 1 For BOM"]:
|
||||
make_item(item, {
|
||||
"include_item_in_manufacturing": 1,
|
||||
"is_stock_item": 1
|
||||
})
|
||||
|
||||
bom_name = frappe.db.get_value("BOM",
|
||||
{"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
|
||||
|
||||
if not bom_name:
|
||||
bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
|
||||
bom.with_operations = 1
|
||||
bom.append("operations", {
|
||||
"operation": "_Test Operation 1",
|
||||
"workstation": "_Test Workstation 1",
|
||||
"description": "Test Data",
|
||||
"operating_cost": 100,
|
||||
"time_in_mins": 40,
|
||||
"batch_size": 5
|
||||
})
|
||||
|
||||
bom.save()
|
||||
bom.submit()
|
||||
bom_name = bom.name
|
||||
|
||||
work_order = make_wo_order_test_record(item=fg_item,
|
||||
planned_start_date=now(), qty=1, do_not_save=True)
|
||||
|
||||
work_order.set_work_order_operations()
|
||||
work_order.save()
|
||||
self.assertEqual(work_order.operations[0].time_in_mins, 8.0)
|
||||
|
||||
work_order1 = make_wo_order_test_record(item=fg_item,
|
||||
planned_start_date=now(), qty=5, do_not_save=True)
|
||||
|
||||
work_order1.set_work_order_operations()
|
||||
work_order1.save()
|
||||
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
|
||||
|
||||
def get_scrap_item_details(bom_no):
|
||||
scrap_items = {}
|
||||
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
|
||||
|
||||
@@ -611,7 +611,7 @@ erpnext.work_order = {
|
||||
description: __('Max: {0}', [max]),
|
||||
default: max
|
||||
}, data => {
|
||||
max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
|
||||
max += (frm.doc.qty * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
|
||||
|
||||
if (data.qty > max) {
|
||||
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
|
||||
|
||||
@@ -364,7 +364,7 @@ class WorkOrder(Document):
|
||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
||||
|
||||
for d in self.get("operations"):
|
||||
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * math.ceil(flt(self.qty) / flt(d.batch_size))
|
||||
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * (flt(self.qty) / flt(d.batch_size))
|
||||
|
||||
self.calculate_operating_cost()
|
||||
|
||||
|
||||
@@ -4,17 +4,16 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("erpnext_integrations", "doctype", "plaid_settings")
|
||||
plaid_settings = frappe.get_single("Plaid Settings")
|
||||
if plaid_settings.enabled:
|
||||
if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env \
|
||||
and frappe.conf.plaid_public_key and frappe.conf.plaid_secret):
|
||||
if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env and frappe.conf.plaid_secret):
|
||||
plaid_settings.enabled = 0
|
||||
else:
|
||||
plaid_settings.update({
|
||||
"plaid_client_id": frappe.conf.plaid_client_id,
|
||||
"plaid_public_key": frappe.conf.plaid_public_key,
|
||||
"plaid_env": frappe.conf.plaid_env,
|
||||
"plaid_secret": frappe.conf.plaid_secret
|
||||
})
|
||||
|
||||
@@ -14,13 +14,15 @@ def get_field_filter_data():
|
||||
for f in fields:
|
||||
doctype = f.get_link_doctype()
|
||||
|
||||
# apply enable/disable filter
|
||||
# apply enable/disable/show_in_website filter
|
||||
meta = frappe.get_meta(doctype)
|
||||
filters = {}
|
||||
if meta.has_field('enabled'):
|
||||
filters['enabled'] = 1
|
||||
if meta.has_field('disabled'):
|
||||
filters['disabled'] = 0
|
||||
if meta.has_field('show_in_website'):
|
||||
filters['show_in_website'] = 1
|
||||
|
||||
values = [d.name for d in frappe.get_all(doctype, filters)]
|
||||
filter_data.append([f, values])
|
||||
@@ -378,7 +380,7 @@ def get_items(filters=None, search=None):
|
||||
|
||||
results = frappe.db.sql('''
|
||||
SELECT
|
||||
`tabItem`.`name`, `tabItem`.`item_name`,
|
||||
`tabItem`.`name`, `tabItem`.`item_name`, `tabItem`.`item_code`,
|
||||
`tabItem`.`website_image`, `tabItem`.`image`,
|
||||
`tabItem`.`web_long_description`, `tabItem`.`description`,
|
||||
`tabItem`.`route`
|
||||
|
||||
@@ -594,7 +594,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
$.each(actual_taxes_dict, function(key, value) {
|
||||
if (value) total_actual_tax += value;
|
||||
});
|
||||
|
||||
|
||||
return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
|
||||
}
|
||||
},
|
||||
@@ -672,25 +672,33 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
);
|
||||
}
|
||||
|
||||
frappe.db.get_value('Sales Invoice Payment', {'parent': this.frm.doc.pos_profile, 'default': 1},
|
||||
['mode_of_payment', 'account', 'type'], (value) => {
|
||||
if (this.frm.is_dirty()) {
|
||||
frappe.model.clear_table(this.frm.doc, 'payments');
|
||||
if (value) {
|
||||
let row = frappe.model.add_child(this.frm.doc, 'Sales Invoice Payment', 'payments');
|
||||
row.mode_of_payment = value.mode_of_payment;
|
||||
row.type = value.type;
|
||||
row.account = value.account;
|
||||
row.default = 1;
|
||||
row.amount = total_amount_to_pay;
|
||||
} else {
|
||||
this.frm.set_value('is_pos', 1);
|
||||
}
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
}, 'Sales Invoice');
|
||||
let existing_amount = 0
|
||||
$.each(this.frm.doc.payments || [], function(i, row) {
|
||||
existing_amount += row.amount;
|
||||
})
|
||||
|
||||
this.calculate_paid_amount();
|
||||
if (existing_amount != total_amount_to_pay) {
|
||||
frappe.db.get_value('Sales Invoice Payment', {'parent': this.frm.doc.pos_profile, 'default': 1},
|
||||
['mode_of_payment', 'account', 'type'], (value) => {
|
||||
if (this.frm.is_dirty()) {
|
||||
frappe.model.clear_table(this.frm.doc, 'payments');
|
||||
if (value) {
|
||||
let row = frappe.model.add_child(this.frm.doc, 'Sales Invoice Payment', 'payments');
|
||||
row.mode_of_payment = value.mode_of_payment;
|
||||
row.type = value.type;
|
||||
row.account = value.account;
|
||||
row.default = 1;
|
||||
row.amount = total_amount_to_pay;
|
||||
} else {
|
||||
this.frm.set_value('is_pos', 1);
|
||||
}
|
||||
this.frm.refresh_fields();
|
||||
this.calculate_paid_amount();
|
||||
}
|
||||
}, 'Sales Invoice');
|
||||
} else {
|
||||
this.calculate_paid_amount();
|
||||
}
|
||||
},
|
||||
|
||||
set_default_payment: function(total_amount_to_pay, update_paid_amount) {
|
||||
|
||||
@@ -452,6 +452,9 @@ erpnext.utils.update_child_items = function(opts) {
|
||||
const frm = opts.frm;
|
||||
const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row;
|
||||
const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname;
|
||||
const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`);
|
||||
const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision;
|
||||
|
||||
this.data = [];
|
||||
const fields = [{
|
||||
fieldtype:'Data',
|
||||
@@ -472,14 +475,16 @@ erpnext.utils.update_child_items = function(opts) {
|
||||
default: 0,
|
||||
read_only: 0,
|
||||
in_list_view: 1,
|
||||
label: __('Qty')
|
||||
label: __('Qty'),
|
||||
precision: get_precision("qty")
|
||||
}, {
|
||||
fieldtype:'Currency',
|
||||
fieldname:"rate",
|
||||
default: 0,
|
||||
read_only: 0,
|
||||
in_list_view: 1,
|
||||
label: __('Rate')
|
||||
label: __('Rate'),
|
||||
precision: get_precision("rate")
|
||||
}];
|
||||
|
||||
if (frm.doc.doctype == 'Sales Order' || frm.doc.doctype == 'Purchase Order' ) {
|
||||
@@ -494,7 +499,8 @@ erpnext.utils.update_child_items = function(opts) {
|
||||
fieldtype: 'Float',
|
||||
fieldname: "conversion_factor",
|
||||
in_list_view: 1,
|
||||
label: __("Conversion Factor")
|
||||
label: __("Conversion Factor"),
|
||||
precision: get_precision('conversion_factor')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -10,5 +10,13 @@ frappe.ui.form.on('Quality Procedure', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('parent_quality_procedure', function(){
|
||||
return {
|
||||
filters: {
|
||||
is_group: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -21,8 +21,7 @@
|
||||
"fieldname": "parent_quality_procedure",
|
||||
"fieldtype": "Link",
|
||||
"label": "Parent Procedure",
|
||||
"options": "Quality Procedure",
|
||||
"read_only": 1
|
||||
"options": "Quality Procedure"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -73,7 +72,7 @@
|
||||
],
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-17 17:25:03.434953",
|
||||
"modified": "2020-10-12 16:14:11.167537",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Quality Management",
|
||||
"name": "Quality Procedure",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
from frappe.utils.nestedset import NestedSet, rebuild_tree
|
||||
from frappe import _
|
||||
|
||||
class QualityProcedure(NestedSet):
|
||||
@@ -42,6 +42,8 @@ class QualityProcedure(NestedSet):
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
def set_parent(self):
|
||||
rebuild_tree('Quality Procedure', 'parent_quality_procedure')
|
||||
|
||||
for process in self.processes:
|
||||
# Set parent for only those children who don't have a parent
|
||||
parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"client",
|
||||
"account_number_length",
|
||||
"column_break_2",
|
||||
"client_number",
|
||||
"section_break_4",
|
||||
@@ -57,9 +58,16 @@
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "4",
|
||||
"fieldname": "account_number_length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Account Number Length",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"modified": "2020-04-15 12:59:57.786506",
|
||||
"modified": "2020-10-03 16:52:35.903867",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "DATEV Settings",
|
||||
|
||||
@@ -158,7 +158,7 @@ class GSTR3BReport(Document):
|
||||
|
||||
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
|
||||
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
|
||||
self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
|
||||
self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas", "Registered Regular"], reverse_charge="Y")
|
||||
self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
|
||||
self.set_itc_details(itc_details)
|
||||
|
||||
@@ -197,7 +197,7 @@ class GSTR3BReport(Document):
|
||||
if d["ty"] == 'ISRC':
|
||||
reverse_charge = "Y"
|
||||
itc_type = 'All Other ITC'
|
||||
gst_category = ['Unregistered', 'Overseas']
|
||||
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
|
||||
else:
|
||||
reverse_charge = "N"
|
||||
|
||||
@@ -253,7 +253,7 @@ class GSTR3BReport(Document):
|
||||
def get_total_taxable_value(self, doctype, reverse_charge):
|
||||
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
select gst_category, sum(net_total) as total
|
||||
select gst_category, sum(base_net_total) as total
|
||||
from `tab{doctype}`
|
||||
where docstatus = 1 and month(posting_date) = %s
|
||||
and year(posting_date) = %s and reverse_charge = %s
|
||||
|
||||
@@ -16,6 +16,7 @@ from csv import QUOTE_NONNUMERIC
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
import pandas as pd
|
||||
|
||||
|
||||
@@ -30,20 +31,33 @@ def execute(filters=None):
|
||||
|
||||
def validate(filters):
|
||||
"""Make sure all mandatory filters and settings are present."""
|
||||
if not filters.get('company'):
|
||||
company = filters.get('company')
|
||||
if not company:
|
||||
frappe.throw(_('<b>Company</b> is a mandatory filter.'))
|
||||
|
||||
if not filters.get('from_date'):
|
||||
from_date = filters.get('from_date')
|
||||
if not from_date:
|
||||
frappe.throw(_('<b>From Date</b> is a mandatory filter.'))
|
||||
|
||||
if not filters.get('to_date'):
|
||||
to_date = filters.get('to_date')
|
||||
if not to_date:
|
||||
frappe.throw(_('<b>To Date</b> is a mandatory filter.'))
|
||||
|
||||
validate_fiscal_year(from_date, to_date, company)
|
||||
|
||||
try:
|
||||
frappe.get_doc('DATEV Settings', filters.get('company'))
|
||||
except frappe.DoesNotExistError:
|
||||
frappe.throw(_('Please create <b>DATEV Settings</b> for Company <b>{}</b>.').format(filters.get('company')))
|
||||
|
||||
|
||||
def validate_fiscal_year(from_date, to_date, company):
|
||||
from_fiscal_year = get_fiscal_year(date=from_date, company=company)
|
||||
to_fiscal_year = get_fiscal_year(date=to_date, company=company)
|
||||
if from_fiscal_year != to_fiscal_year:
|
||||
frappe.throw(_('Dates {} and {} are not in the same fiscal year.').format(from_date, to_date))
|
||||
|
||||
|
||||
def get_columns():
|
||||
"""Return the list of columns that will be shown in query report."""
|
||||
columns = [
|
||||
@@ -231,9 +245,9 @@ def get_datev_csv(data, filters):
|
||||
# L = Tax client number (Mandantennummer)
|
||||
frappe.get_value("DATEV Settings", filters.get("company"), "client_number") or "",
|
||||
# M = Start of the fiscal year (Wirtschaftsjahresbeginn)
|
||||
frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"),
|
||||
frappe.utils.formatdate(filters.get("fiscal_year_start"), "yyyyMMdd"),
|
||||
# N = Length of account numbers (Sachkontenlänge)
|
||||
"4",
|
||||
str(filters.get('account_number_length', 4)),
|
||||
# O = Transaction batch start date (YYYYMMDD)
|
||||
frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd"),
|
||||
# P = Transaction batch end date (YYYYMMDD)
|
||||
@@ -507,6 +521,12 @@ def download_datev_csv(filters=None):
|
||||
filters = json.loads(filters)
|
||||
|
||||
validate(filters)
|
||||
|
||||
filters['account_number_length'] = frappe.get_value('DATEV Settings', filters.get('company'), 'account_number_length')
|
||||
|
||||
fiscal_year = get_fiscal_year(date=filters.get('from_date'), company=filters.get('company'))
|
||||
filters['fiscal_year_start'] = fiscal_year[1]
|
||||
|
||||
data = get_gl_entries(filters, as_dict=1)
|
||||
|
||||
frappe.response['result'] = get_datev_csv(data, filters)
|
||||
|
||||
@@ -129,7 +129,7 @@ class Customer(TransactionBase):
|
||||
address = frappe.get_doc('Address', address_name.get('name'))
|
||||
if not address.has_link('Customer', self.name):
|
||||
address.append('links', dict(link_doctype='Customer', link_name=self.name))
|
||||
address.save()
|
||||
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)
|
||||
|
||||
@@ -147,7 +147,7 @@ class Customer(TransactionBase):
|
||||
contact = frappe.get_doc('Contact', contact_name.get('name'))
|
||||
if not contact.has_link('Customer', self.name):
|
||||
contact.append('links', dict(link_doctype='Customer', link_name=self.name))
|
||||
contact.save()
|
||||
contact.save(ignore_permissions=self.flags.ignore_permissions)
|
||||
|
||||
else:
|
||||
lead.lead_name = lead.lead_name.lstrip().split(" ")
|
||||
|
||||
@@ -108,6 +108,10 @@ class TestQuotation(unittest.TestCase):
|
||||
sales_order.transaction_date = nowdate()
|
||||
sales_order.insert()
|
||||
|
||||
# Remove any unknown taxes if applied
|
||||
sales_order.set('taxes', [])
|
||||
sales_order.save()
|
||||
|
||||
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 8906.00)
|
||||
self.assertEqual(sales_order.payment_schedule[0].due_date, getdate(quotation.transaction_date))
|
||||
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 8906.00)
|
||||
|
||||
@@ -169,7 +169,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
}
|
||||
|
||||
// project
|
||||
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) {
|
||||
if(flt(doc.per_delivered, 2) < 100) {
|
||||
this.frm.add_custom_button(__('Project'), () => this.make_project(), __('Create'));
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,8 @@ class TestSalesOrder(unittest.TestCase):
|
||||
self.assertEqual(len(si.get("items")), 1)
|
||||
|
||||
si.insert()
|
||||
si.set('taxes', [])
|
||||
si.save()
|
||||
|
||||
self.assertEqual(si.payment_schedule[0].payment_amount, 500.0)
|
||||
self.assertEqual(si.payment_schedule[0].due_date, so.transaction_date)
|
||||
@@ -401,6 +403,22 @@ class TestSalesOrder(unittest.TestCase):
|
||||
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}])
|
||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
|
||||
|
||||
def test_update_child_with_precision(self):
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
|
||||
precision = get_field_precision(frappe.get_meta("Sales Order Item").get_field("rate"))
|
||||
|
||||
make_property_setter("Sales Order Item", "rate", "precision", 7, "Currency")
|
||||
so = make_sales_order(item_code= "_Test Item", qty=4, rate=200.34664)
|
||||
|
||||
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200.34669, 'qty' : 4, 'docname': so.items[0].name}])
|
||||
update_child_qty_rate('Sales Order', trans_item, so.name)
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.items[0].rate, 200.34669)
|
||||
make_property_setter("Sales Order Item", "rate", "precision", precision, "Currency")
|
||||
|
||||
def test_update_child_qty_rate_perm(self):
|
||||
so = make_sales_order(item_code= "_Test Item", qty=4)
|
||||
|
||||
@@ -425,9 +443,9 @@ class TestSalesOrder(unittest.TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
workflow = make_sales_order_workflow()
|
||||
so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
|
||||
frappe.set_user("Administrator")
|
||||
apply_workflow(so, 'Approve')
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
user = 'test@example.com'
|
||||
test_user = frappe.get_doc('User', user)
|
||||
test_user.add_roles("Sales User", "Test Junior Approver")
|
||||
@@ -474,6 +492,95 @@ class TestSalesOrder(unittest.TestCase):
|
||||
so.reload()
|
||||
self.assertEqual(so.packed_items[0].qty, 4)
|
||||
|
||||
def test_update_child_with_tax_template(self):
|
||||
"""
|
||||
Test Action: Create a SO with one item having its tax account head already in the SO.
|
||||
Add the same item + new item with tax template via Update Items.
|
||||
Expected result: First Item's tax row is updated. New tax row is added for second Item.
|
||||
"""
|
||||
if not frappe.db.exists("Item", "Test Item with Tax"):
|
||||
make_item("Test Item with Tax", {
|
||||
'is_stock_item': 1,
|
||||
})
|
||||
|
||||
if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Item Tax Template',
|
||||
'title': 'Test Update Items Template',
|
||||
'company': '_Test Company',
|
||||
'taxes': [
|
||||
{
|
||||
'tax_type': "_Test Account Service Tax - _TC",
|
||||
'tax_rate': 10,
|
||||
}
|
||||
]
|
||||
}).insert()
|
||||
|
||||
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
|
||||
|
||||
new_item_with_tax.append("taxes", {
|
||||
"item_tax_template": "Test Update Items Template",
|
||||
"valid_from": nowdate()
|
||||
})
|
||||
new_item_with_tax.save()
|
||||
|
||||
tax_template = "_Test Account Excise Duty @ 10"
|
||||
item = "_Test Item Home Desktop 100"
|
||||
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
||||
item_doc = frappe.get_doc("Item", item)
|
||||
item_doc.append("taxes", {
|
||||
"item_tax_template": tax_template,
|
||||
"valid_from": nowdate()
|
||||
})
|
||||
item_doc.save()
|
||||
else:
|
||||
# update valid from
|
||||
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
|
||||
where parent = %(item)s and item_tax_template = %(tax)s""",
|
||||
{"item": item, "tax": tax_template})
|
||||
|
||||
so = make_sales_order(item_code=item, qty=1, do_not_save=1)
|
||||
|
||||
so.append("taxes", {
|
||||
"account_head": "_Test Account Excise Duty - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Excise Duty",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 10
|
||||
})
|
||||
so.insert()
|
||||
so.submit()
|
||||
|
||||
self.assertEqual(so.taxes[0].tax_amount, 10)
|
||||
self.assertEqual(so.taxes[0].total, 110)
|
||||
|
||||
old_stock_settings_value = frappe.db.get_single_value("Stock Settings", "default_warehouse")
|
||||
frappe.db.set_value("Stock Settings", None, "default_warehouse", "_Test Warehouse - _TC")
|
||||
|
||||
items = json.dumps([
|
||||
{'item_code' : item, 'rate' : 100, 'qty' : 1, 'docname': so.items[0].name},
|
||||
{'item_code' : item, 'rate' : 200, 'qty' : 1}, # added item whose tax account head already exists in PO
|
||||
{'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO
|
||||
])
|
||||
update_child_qty_rate('Sales Order', items, so.name)
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.taxes[0].tax_amount, 40)
|
||||
self.assertEqual(so.taxes[0].total, 440)
|
||||
self.assertEqual(so.taxes[1].account_head, "_Test Account Service Tax - _TC")
|
||||
self.assertEqual(so.taxes[1].tax_amount, 40)
|
||||
self.assertEqual(so.taxes[1].total, 480)
|
||||
|
||||
# teardown
|
||||
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
|
||||
where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template})
|
||||
so.cancel()
|
||||
so.delete()
|
||||
new_item_with_tax.delete()
|
||||
frappe.get_doc("Item Tax Template", "Test Update Items Template").delete()
|
||||
frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value)
|
||||
|
||||
def test_warehouse_user(self):
|
||||
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
|
||||
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
|
||||
@@ -1010,7 +1117,7 @@ def make_sales_order_workflow():
|
||||
"is_active": 1,
|
||||
"send_email_alert": 0,
|
||||
})
|
||||
workflow.append('states', dict( state = 'Pending', allow_edit = 'All' ))
|
||||
workflow.append('states', dict( state = 'Pending', allow_edit = 'Administrator' ))
|
||||
workflow.append('states', dict( state = 'Approved', allow_edit = 'Test Approver', doc_status = 1 ))
|
||||
workflow.append('transitions', dict(
|
||||
state = 'Pending', action = 'Approve', next_state = 'Approved', allowed = 'Test Junior Approver', allow_self_approval = 1,
|
||||
|
||||
@@ -372,7 +372,7 @@ class Company(NestedSet):
|
||||
|
||||
@frappe.whitelist()
|
||||
def enqueue_replace_abbr(company, old, new):
|
||||
kwargs = dict(company=company, old=old, new=new)
|
||||
kwargs = dict(queue='long', company=company, old=old, new=new)
|
||||
frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, cint
|
||||
from frappe import msgprint, throw, _
|
||||
|
||||
from frappe.model.document import Document
|
||||
@@ -159,7 +159,7 @@ class NamingSeries(Document):
|
||||
prefix = self.parse_naming_series()
|
||||
self.insert_series(prefix)
|
||||
frappe.db.sql("update `tabSeries` set current = %s where name = %s",
|
||||
(self.current_value, prefix))
|
||||
(cint(self.current_value), prefix))
|
||||
msgprint(_("Series Updated Successfully"))
|
||||
else:
|
||||
msgprint(_("Please select prefix first"))
|
||||
|
||||
@@ -111,6 +111,7 @@ class Item(WebsiteGenerator):
|
||||
self.synced_with_hub = 0
|
||||
|
||||
self.validate_has_variants()
|
||||
self.validate_attributes_in_variants()
|
||||
self.validate_stock_exists_for_template_item()
|
||||
self.validate_attributes()
|
||||
self.validate_variant_attributes()
|
||||
@@ -806,6 +807,76 @@ class Item(WebsiteGenerator):
|
||||
if frappe.db.exists("Item", {"variant_of": self.name}):
|
||||
frappe.throw(_("Item has variants."))
|
||||
|
||||
def validate_attributes_in_variants(self):
|
||||
if not self.has_variants or self.get("__islocal"):
|
||||
return
|
||||
|
||||
old_doc = self.get_doc_before_save()
|
||||
old_doc_attributes = set([attr.attribute for attr in old_doc.attributes])
|
||||
own_attributes = [attr.attribute for attr in self.attributes]
|
||||
|
||||
# Check if old attributes were removed from the list
|
||||
# Is old_attrs is a subset of new ones
|
||||
# that means we need not check any changes
|
||||
if old_doc_attributes.issubset(set(own_attributes)):
|
||||
return
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
# get all item variants
|
||||
items = [item["name"] for item in frappe.get_all("Item", {"variant_of": self.name})]
|
||||
|
||||
# get all deleted attributes
|
||||
deleted_attribute = list(old_doc_attributes.difference(set(own_attributes)))
|
||||
|
||||
# fetch all attributes of these items
|
||||
item_attributes = frappe.get_all(
|
||||
"Item Variant Attribute",
|
||||
filters={
|
||||
"parent": ["in", items],
|
||||
"attribute": ["in", deleted_attribute]
|
||||
},
|
||||
fields=["attribute", "parent"]
|
||||
)
|
||||
not_included = defaultdict(list)
|
||||
|
||||
for attr in item_attributes:
|
||||
if attr["attribute"] not in own_attributes:
|
||||
not_included[attr["parent"]].append(attr["attribute"])
|
||||
|
||||
if not len(not_included):
|
||||
return
|
||||
|
||||
def body(docnames):
|
||||
docnames.sort()
|
||||
return "<br>".join(docnames)
|
||||
|
||||
def table_row(title, body):
|
||||
return """<tr>
|
||||
<td>{0}</td>
|
||||
<td>{1}</td>
|
||||
</tr>""".format(title, body)
|
||||
|
||||
rows = ''
|
||||
for docname, attr_list in not_included.items():
|
||||
link = "<a href='#Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
|
||||
rows += table_row(link, body(attr_list))
|
||||
|
||||
error_description = _('The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template.')
|
||||
|
||||
message = """
|
||||
<div>{0}</div><br>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<td>{1}</td>
|
||||
<td>{2}</td>
|
||||
</thead>
|
||||
{3}
|
||||
</table>
|
||||
""".format(error_description, _('Variant Items'), _('Attributes'), rows)
|
||||
|
||||
frappe.throw(message, title=_("Variant Attribute Error"), is_minimizable=True)
|
||||
|
||||
def validate_stock_exists_for_template_item(self):
|
||||
if self.stock_ledger_created() and self._doc_before_save:
|
||||
if (cint(self._doc_before_save.has_variants) != cint(self.has_variants)
|
||||
@@ -1001,8 +1072,8 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
order by pr.posting_date desc, pr.posting_time desc, pr.name desc
|
||||
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
|
||||
or "1900-01-01")
|
||||
purchase_receipt_date = getdate(last_purchase_receipt and
|
||||
@@ -1010,7 +1081,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
|
||||
if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt):
|
||||
# use purchase order
|
||||
|
||||
|
||||
last_purchase = last_purchase_order[0]
|
||||
purchase_date = purchase_order_date
|
||||
|
||||
@@ -1030,7 +1101,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
"discount_percentage": flt(last_purchase.discount_percentage),
|
||||
"purchase_date": purchase_date
|
||||
})
|
||||
|
||||
|
||||
|
||||
conversion_rate = flt(conversion_rate) or 1.0
|
||||
out.update({
|
||||
@@ -1069,8 +1140,7 @@ def invalidate_item_variants_cache_for_website(doc):
|
||||
|
||||
if item_code:
|
||||
item_cache = ItemVariantsCacheManager(item_code)
|
||||
item_cache.clear_cache()
|
||||
|
||||
item_cache.rebuild_cache()
|
||||
|
||||
def check_stock_uom_with_bin(item, stock_uom):
|
||||
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
|
||||
|
||||
@@ -1,357 +1,97 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:attribute_name",
|
||||
"beta": 0,
|
||||
"creation": "2014-09-26 03:49:54.899170",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:attribute_name",
|
||||
"creation": "2014-09-26 03:49:54.899170",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"attribute_name",
|
||||
"numeric_values",
|
||||
"section_break_4",
|
||||
"from_range",
|
||||
"increment",
|
||||
"column_break_8",
|
||||
"to_range",
|
||||
"section_break_5",
|
||||
"item_attribute_values"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "attribute_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Attribute Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"fieldname": "attribute_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Attribute Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "numeric_values",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Numeric Values",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "numeric_values",
|
||||
"fieldtype": "Check",
|
||||
"label": "Numeric Values"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "numeric_values",
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "numeric_values",
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "from_range",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Range",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "from_range",
|
||||
"fieldtype": "Float",
|
||||
"label": "From Range"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "increment",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Increment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "increment",
|
||||
"fieldtype": "Float",
|
||||
"label": "Increment"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "to_range",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To Range",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "to_range",
|
||||
"fieldtype": "Float",
|
||||
"label": "To Range"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: !doc.numeric_values",
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval: !doc.numeric_values",
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "item_attribute_values",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Item Attribute Values",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Item Attribute Value",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "item_attribute_values",
|
||||
"fieldtype": "Table",
|
||||
"label": "Item Attribute Values",
|
||||
"options": "Item Attribute Value"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-edit",
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-01-01 13:17:46.524806",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Attribute",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-edit",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-02 12:03:02.359202",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Attribute",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Item Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Item Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.controllers.item_variant import (validate_is_incremental,
|
||||
validate_item_attribute_value, InvalidItemAttributeValueError)
|
||||
@@ -42,7 +43,7 @@ class ItemAttribute(Document):
|
||||
if self.from_range is None or self.to_range is None:
|
||||
frappe.throw(_("Please specify from/to range"))
|
||||
|
||||
elif self.from_range >= self.to_range:
|
||||
elif flt(self.from_range) >= flt(self.to_range):
|
||||
frappe.throw(_("From Range has to be less than To Range"))
|
||||
|
||||
if not self.increment:
|
||||
|
||||
@@ -50,16 +50,18 @@ class ItemPrice(Document):
|
||||
|
||||
def check_duplicates(self):
|
||||
conditions = "where item_code=%(item_code)s and price_list=%(price_list)s and name != %(name)s"
|
||||
condition_data_dict = dict(item_code=self.item_code, price_list=self.price_list, name=self.name)
|
||||
|
||||
for field in ['uom', 'valid_from',
|
||||
'valid_upto', 'packing_unit', 'customer', 'supplier']:
|
||||
if self.get(field):
|
||||
conditions += " and {0} = %({1})s".format(field, field)
|
||||
condition_data_dict[field] = self.get(field)
|
||||
|
||||
price_list_rate = frappe.db.sql("""
|
||||
SELECT price_list_rate
|
||||
FROM `tabItem Price`
|
||||
{conditions} """.format(conditions=conditions), self.as_dict())
|
||||
{conditions} """.format(conditions=conditions), condition_data_dict)
|
||||
|
||||
if price_list_rate :
|
||||
frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates."), ItemPriceDuplicateItem)
|
||||
|
||||
@@ -207,7 +207,7 @@ class PurchaseReceipt(BuyingController):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
|
||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||
cogs_account = self.get_company_default("default_expense_account")
|
||||
stock_rbnb_currency = get_account_currency(stock_rbnb)
|
||||
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
|
||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
|
||||
@@ -243,7 +243,6 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
# stock received but not billed
|
||||
if d.base_net_amount:
|
||||
stock_rbnb_currency = get_account_currency(stock_rbnb)
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": stock_rbnb,
|
||||
"against": warehouse_account[d.warehouse]["account"],
|
||||
@@ -289,6 +288,7 @@ class PurchaseReceipt(BuyingController):
|
||||
if self.is_return or flt(d.item_tax_amount):
|
||||
loss_account = expenses_included_in_valuation
|
||||
else:
|
||||
cogs_account = self.get_company_default("default_expense_account")
|
||||
loss_account = cogs_account
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
|
||||
@@ -471,8 +471,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
"expected_value_after_useful_life": 10,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 1,
|
||||
"depreciation_start_date": frappe.utils.nowdate()
|
||||
"frequency_of_depreciation": 1
|
||||
})
|
||||
asset.submit()
|
||||
|
||||
@@ -614,9 +613,9 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
rm_items = [
|
||||
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
|
||||
"qty":300,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||
"qty":300,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name},
|
||||
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
|
||||
"qty":200,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}
|
||||
"qty":200,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name}
|
||||
]
|
||||
|
||||
rm_item_string = json.dumps(rm_items)
|
||||
|
||||
@@ -24,6 +24,24 @@ frappe.ui.form.on('Stock Entry', {
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query('source_warehouse_address', function() {
|
||||
return {
|
||||
filters: {
|
||||
link_doctype: 'Warehouse',
|
||||
link_name: frm.doc.from_warehouse
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query('target_warehouse_address', function() {
|
||||
return {
|
||||
filters: {
|
||||
link_doctype: 'Warehouse',
|
||||
link_name: frm.doc.to_warehouse
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.db.get_value('Stock Settings', {name: 'Stock Settings'}, 'sample_retention_warehouse', (r) => {
|
||||
if (r.sample_retention_warehouse) {
|
||||
var filters = [
|
||||
@@ -139,6 +157,7 @@ frappe.ui.form.on('Stock Entry', {
|
||||
mr_item.item_code = item.item_code;
|
||||
mr_item.item_name = item.item_name;
|
||||
mr_item.uom = item.uom;
|
||||
mr_item.stock_uom = item.stock_uom;
|
||||
mr_item.conversion_factor = item.conversion_factor;
|
||||
mr_item.item_group = item.item_group;
|
||||
mr_item.description = item.description;
|
||||
|
||||
@@ -844,6 +844,8 @@ class StockEntry(StockController):
|
||||
frappe.throw(_("Posting date and posting time is mandatory"))
|
||||
|
||||
self.set_work_order_details()
|
||||
self.flags.backflush_based_on = frappe.db.get_single_value("Manufacturing Settings",
|
||||
"backflush_raw_materials_based_on")
|
||||
|
||||
if self.bom_no:
|
||||
|
||||
@@ -857,14 +859,14 @@ class StockEntry(StockController):
|
||||
item["to_warehouse"] = self.pro_doc.wip_warehouse
|
||||
self.add_to_stock_entry_detail(item_dict)
|
||||
|
||||
elif (self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
|
||||
and not self.pro_doc.skip_transfer and frappe.db.get_single_value("Manufacturing Settings",
|
||||
"backflush_raw_materials_based_on")== "Material Transferred for Manufacture"):
|
||||
elif (self.work_order and (self.purpose == "Manufacture"
|
||||
or self.purpose == "Material Consumption for Manufacture") and not self.pro_doc.skip_transfer
|
||||
and self.flags.backflush_based_on == "Material Transferred for Manufacture"):
|
||||
self.get_transfered_raw_materials()
|
||||
|
||||
elif self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture") and \
|
||||
frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")== "BOM" and \
|
||||
frappe.db.get_single_value("Manufacturing Settings", "material_consumption")== 1:
|
||||
elif (self.work_order and (self.purpose == "Manufacture" or
|
||||
self.purpose == "Material Consumption for Manufacture") and self.flags.backflush_based_on== "BOM"
|
||||
and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")== 1):
|
||||
self.get_unconsumed_raw_materials()
|
||||
else:
|
||||
if not self.fg_completed_qty:
|
||||
@@ -1108,7 +1110,6 @@ class StockEntry(StockController):
|
||||
for d in backflushed_materials.get(item.item_code):
|
||||
if d.get(item.warehouse):
|
||||
if (qty > req_qty):
|
||||
qty = req_qty
|
||||
qty-= d.get(item.warehouse)
|
||||
|
||||
if qty > 0:
|
||||
@@ -1133,11 +1134,22 @@ class StockEntry(StockController):
|
||||
"""
|
||||
item_dict = self.get_pro_order_required_items()
|
||||
max_qty = flt(self.pro_doc.qty)
|
||||
|
||||
allow_overproduction = False
|
||||
overproduction_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
|
||||
"overproduction_percentage_for_work_order"))
|
||||
|
||||
to_transfer_qty = flt(self.pro_doc.material_transferred_for_manufacturing) + flt(self.fg_completed_qty)
|
||||
transfer_limit_qty = max_qty + ((max_qty * overproduction_percentage) / 100)
|
||||
|
||||
if transfer_limit_qty >= to_transfer_qty:
|
||||
allow_overproduction = True
|
||||
|
||||
for item, item_details in iteritems(item_dict):
|
||||
pending_to_issue = flt(item_details.required_qty) - flt(item_details.transferred_qty)
|
||||
desire_to_transfer = flt(self.fg_completed_qty) * flt(item_details.required_qty) / max_qty
|
||||
|
||||
if desire_to_transfer <= pending_to_issue:
|
||||
if desire_to_transfer <= pending_to_issue or allow_overproduction:
|
||||
item_dict[item]["qty"] = desire_to_transfer
|
||||
elif pending_to_issue > 0:
|
||||
item_dict[item]["qty"] = pending_to_issue
|
||||
@@ -1255,8 +1267,9 @@ class StockEntry(StockController):
|
||||
FROM
|
||||
`tabStock Entry Detail` sed, `tabStock Entry` se
|
||||
WHERE
|
||||
(pos.name = sed.po_detail OR sed.subcontracted_item = pos.main_item_code)
|
||||
AND sed.docstatus = 1 AND se.name = sed.parent and se.purchase_order = %(po)s
|
||||
pos.name = sed.po_detail AND pos.rm_item_code = sed.item_code
|
||||
AND pos.parent = se.purchase_order AND sed.docstatus = 1
|
||||
AND se.name = sed.parent and se.purchase_order = %(po)s
|
||||
), 0)
|
||||
WHERE pos.docstatus = 1 and pos.parent = %(po)s""", {"po": self.purchase_order})
|
||||
|
||||
|
||||
@@ -109,6 +109,10 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
|
||||
frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty);
|
||||
frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
|
||||
|
||||
if (frm.doc.purpose == "Stock Reconciliation" && !d.serial_no) {
|
||||
frappe.model.set_value(cdt, cdn, "serial_no", r.message.serial_nos);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -183,6 +187,11 @@ frappe.ui.form.on("Stock Reconciliation Item", {
|
||||
frappe.model.set_value(cdt, cdn, "batch_no", "");
|
||||
}
|
||||
|
||||
if (child.serial_no) {
|
||||
frappe.model.set_value(cdt, cdn, "serial_no", "");
|
||||
frappe.model.set_value(cdt, cdn, "current_serial_no", "");
|
||||
}
|
||||
|
||||
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ class StockReconciliation(StockController):
|
||||
|
||||
if item_dict.get("serial_nos"):
|
||||
item.current_serial_no = item_dict.get("serial_nos")
|
||||
if self.purpose == "Stock Reconciliation" and not item.serial_no:
|
||||
item.serial_no = item.current_serial_no
|
||||
|
||||
item.current_qty = item_dict.get("qty")
|
||||
item.current_valuation_rate = item_dict.get("rate")
|
||||
@@ -172,8 +174,9 @@ class StockReconciliation(StockController):
|
||||
row.serial_no = ''
|
||||
|
||||
# item managed batch-wise not allowed
|
||||
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
|
||||
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
|
||||
if item.has_batch_no and not row.batch_no and not frappe.flags.in_test:
|
||||
if not item.create_new_batch or self.purpose != 'Opening Stock':
|
||||
raise frappe.ValidationError(_("Batch no is required for the batched item {0}").format(item_code))
|
||||
|
||||
# docstatus should be < 2
|
||||
validate_cancelled_item(item_code, item.docstatus, verbose=0)
|
||||
@@ -191,10 +194,11 @@ class StockReconciliation(StockController):
|
||||
serialized_items = False
|
||||
for row in self.items:
|
||||
item = frappe.get_cached_doc("Item", row.item_code)
|
||||
if not (item.has_serial_no or item.has_batch_no):
|
||||
if row.serial_no or row.batch_no:
|
||||
if not (item.has_serial_no):
|
||||
if row.serial_no:
|
||||
frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \
|
||||
.format(row.idx, frappe.bold(row.item_code)))
|
||||
|
||||
previous_sle = get_previous_sle({
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
@@ -217,7 +221,12 @@ class StockReconciliation(StockController):
|
||||
or (not previous_sle and not row.qty)):
|
||||
continue
|
||||
|
||||
sl_entries.append(self.get_sle_for_items(row))
|
||||
sle_data = self.get_sle_for_items(row)
|
||||
|
||||
if row.batch_no:
|
||||
sle_data.actual_qty = row.quantity_difference
|
||||
|
||||
sl_entries.append(sle_data)
|
||||
|
||||
else:
|
||||
serialized_items = True
|
||||
@@ -244,7 +253,7 @@ class StockReconciliation(StockController):
|
||||
serial_nos = get_serial_nos(row.serial_no) or []
|
||||
|
||||
# To issue existing serial nos
|
||||
if row.current_qty and (row.current_serial_no or row.batch_no):
|
||||
if row.current_qty and (row.current_serial_no):
|
||||
args = self.get_sle_for_items(row)
|
||||
args.update({
|
||||
'actual_qty': -1 * row.current_qty,
|
||||
|
||||
@@ -131,7 +131,7 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
to_delete_records.append(sr.name)
|
||||
|
||||
sr = create_stock_reconciliation(item_code=serial_item_code,
|
||||
warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos))
|
||||
warehouse = serial_warehouse, qty=5, rate=300)
|
||||
|
||||
# print(sr.name)
|
||||
serial_nos1 = get_serial_nos(sr.items[0].serial_no)
|
||||
@@ -361,6 +361,37 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
doc.cancel()
|
||||
frappe.delete_doc(doc.doctype, doc.name)
|
||||
|
||||
def test_allow_negative_for_batch(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
item_code = "Stock-Reco-batch-Item-5"
|
||||
warehouse = "_Test Warehouse for Stock Reco5 - _TC"
|
||||
|
||||
create_warehouse("_Test Warehouse for Stock Reco5", {"is_group": 0,
|
||||
"parent_warehouse": "_Test Warehouse Group - _TC", "company": "_Test Company"})
|
||||
|
||||
batch_item_doc = create_item(item_code, is_stock_item=1)
|
||||
if not batch_item_doc.has_batch_no:
|
||||
frappe.db.set_value("Item", item_code, {
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "Test-C.####"
|
||||
})
|
||||
|
||||
ste1=make_stock_entry(posting_date="2020-10-07", posting_time="02:00", item_code=item_code,
|
||||
target=warehouse, qty=2, basic_rate=100)
|
||||
|
||||
batch_no = ste1.items[0].batch_no
|
||||
|
||||
ste2=make_stock_entry(posting_date="2020-10-09", posting_time="02:00", item_code=item_code,
|
||||
source=warehouse, qty=2, basic_rate=100, batch_no=batch_no)
|
||||
|
||||
sr = create_stock_reconciliation(item_code=item_code,
|
||||
warehouse = warehouse, batch_no=batch_no, rate=200)
|
||||
|
||||
for doc in [sr, ste2, ste1]:
|
||||
doc.cancel()
|
||||
frappe.delete_doc(doc.doctype, doc.name)
|
||||
|
||||
def insert_existing_sle(warehouse):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
|
||||
@@ -385,6 +385,11 @@ def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
|
||||
else:
|
||||
warehouse = args.get('warehouse')
|
||||
|
||||
if not warehouse:
|
||||
default_warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse")
|
||||
if frappe.db.get_value("Warehouse", default_warehouse, "company") == args.company:
|
||||
return default_warehouse
|
||||
|
||||
return warehouse
|
||||
|
||||
def update_barcode_value(out):
|
||||
|
||||
@@ -3,19 +3,70 @@
|
||||
|
||||
frappe.query_reports["Batch-Wise Balance History"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": "80",
|
||||
"default": frappe.sys_defaults.year_start_date,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": "80",
|
||||
"default": frappe.datetime.get_today()
|
||||
}
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"item_code",
|
||||
"label": __("Item Code"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"get_query": function() {
|
||||
return {
|
||||
filters: {
|
||||
"has_batch_no": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"warehouse",
|
||||
"label": __("Warehouse"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"get_query": function() {
|
||||
let company = frappe.query_report.get_filter_value('company');
|
||||
return {
|
||||
filters: {
|
||||
"company": company
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"batch_no",
|
||||
"label": __("Batch No"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Batch",
|
||||
"get_query": function() {
|
||||
let item_code = frappe.query_report.get_filter_value('item_code');
|
||||
return {
|
||||
filters: {
|
||||
"item": item_code
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,9 @@ from frappe.utils import flt, cint, getdate
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
|
||||
if filters.from_date > filters.to_date:
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
float_precision = cint(frappe.db.get_default("float_precision")) or 3
|
||||
|
||||
columns = get_columns(filters)
|
||||
@@ -50,6 +53,10 @@ def get_conditions(filters):
|
||||
else:
|
||||
frappe.throw(_("'To Date' is required"))
|
||||
|
||||
for field in ["item_code", "warehouse", "batch_no", "company"]:
|
||||
if filters.get(field):
|
||||
conditions += " and {0} = {1}".format(field, frappe.db.escape(filters.get(field)))
|
||||
|
||||
return conditions
|
||||
|
||||
#get all details
|
||||
|
||||
@@ -77,38 +77,33 @@ def get_price_list():
|
||||
return item_rate_map
|
||||
|
||||
def get_last_purchase_rate():
|
||||
|
||||
item_last_purchase_rate_map = {}
|
||||
|
||||
query = """select * from (select
|
||||
result.item_code,
|
||||
result.base_rate
|
||||
from (
|
||||
(select
|
||||
po_item.item_code,
|
||||
po_item.item_name,
|
||||
po.transaction_date as posting_date,
|
||||
po_item.base_price_list_rate,
|
||||
po_item.discount_percentage,
|
||||
po_item.base_rate
|
||||
from `tabPurchase Order` po, `tabPurchase Order Item` po_item
|
||||
where po.name = po_item.parent and po.docstatus = 1)
|
||||
union
|
||||
(select
|
||||
pr_item.item_code,
|
||||
pr_item.item_name,
|
||||
pr.posting_date,
|
||||
pr_item.base_price_list_rate,
|
||||
pr_item.discount_percentage,
|
||||
pr_item.base_rate
|
||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
|
||||
where pr.name = pr_item.parent and pr.docstatus = 1)
|
||||
) result
|
||||
order by result.item_code asc, result.posting_date desc) result_wrapper
|
||||
group by item_code"""
|
||||
query = """select * from (
|
||||
(select
|
||||
po_item.item_code,
|
||||
po.transaction_date as posting_date,
|
||||
po_item.base_rate
|
||||
from `tabPurchase Order` po, `tabPurchase Order Item` po_item
|
||||
where po.name = po_item.parent and po.docstatus = 1)
|
||||
union
|
||||
(select
|
||||
pr_item.item_code,
|
||||
pr.posting_date,
|
||||
pr_item.base_rate
|
||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
|
||||
where pr.name = pr_item.parent and pr.docstatus = 1)
|
||||
union
|
||||
(select
|
||||
pi_item.item_code,
|
||||
pi.posting_date,
|
||||
pi_item.base_rate
|
||||
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item
|
||||
where pi.name = pi_item.parent and pi.docstatus = 1 and pi.update_stock = 1)
|
||||
) result order by result.item_code asc, result.posting_date asc"""
|
||||
|
||||
for d in frappe.db.sql(query, as_dict=1):
|
||||
item_last_purchase_rate_map.setdefault(d.item_code, d.base_rate)
|
||||
item_last_purchase_rate_map[d.item_code] = d.base_rate
|
||||
|
||||
return item_last_purchase_rate_map
|
||||
|
||||
|
||||
@@ -17,14 +17,17 @@ def execute(filters=None):
|
||||
|
||||
data = []
|
||||
for item, item_dict in iteritems(item_details):
|
||||
earliest_age, latest_age = 0, 0
|
||||
|
||||
fifo_queue = sorted(filter(_func, item_dict["fifo_queue"]), key=_func)
|
||||
details = item_dict["details"]
|
||||
if not fifo_queue or (not item_dict.get("total_qty")): continue
|
||||
if not fifo_queue and (not item_dict.get("total_qty")): continue
|
||||
|
||||
average_age = get_average_age(fifo_queue, to_date)
|
||||
earliest_age = date_diff(to_date, fifo_queue[0][1])
|
||||
latest_age = date_diff(to_date, fifo_queue[-1][1])
|
||||
|
||||
if fifo_queue:
|
||||
earliest_age = date_diff(to_date, fifo_queue[0][1])
|
||||
latest_age = date_diff(to_date, fifo_queue[-1][1])
|
||||
|
||||
row = [details.name, details.item_name,
|
||||
details.description, details.item_group, details.brand]
|
||||
@@ -147,7 +150,8 @@ def get_fifo_queue(filters, sle=None):
|
||||
item_details.setdefault(key, {"details": d, "fifo_queue": []})
|
||||
fifo_queue = item_details[key]["fifo_queue"]
|
||||
|
||||
transferred_item_details.setdefault((d.voucher_no, d.name), [])
|
||||
transferred_item_key = (d.voucher_no, d.name, d.warehouse)
|
||||
transferred_item_details.setdefault(transferred_item_key, [])
|
||||
|
||||
if d.voucher_type == "Stock Reconciliation":
|
||||
d.actual_qty = flt(d.qty_after_transaction) - flt(item_details[key].get("qty_after_transaction", 0))
|
||||
@@ -155,10 +159,10 @@ def get_fifo_queue(filters, sle=None):
|
||||
serial_no_list = get_serial_nos(d.serial_no) if d.serial_no else []
|
||||
|
||||
if d.actual_qty > 0:
|
||||
if transferred_item_details.get((d.voucher_no, d.name)):
|
||||
batch = transferred_item_details[(d.voucher_no, d.name)][0]
|
||||
if transferred_item_details.get(transferred_item_key):
|
||||
batch = transferred_item_details[transferred_item_key][0]
|
||||
fifo_queue.append(batch)
|
||||
transferred_item_details[((d.voucher_no, d.name))].pop(0)
|
||||
transferred_item_details[transferred_item_key].pop(0)
|
||||
else:
|
||||
if serial_no_list:
|
||||
for serial_no in serial_no_list:
|
||||
@@ -182,11 +186,11 @@ def get_fifo_queue(filters, sle=None):
|
||||
# if batch qty > 0
|
||||
# not enough or exactly same qty in current batch, clear batch
|
||||
qty_to_pop -= flt(batch[0])
|
||||
transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
|
||||
transferred_item_details[transferred_item_key].append(fifo_queue.pop(0))
|
||||
else:
|
||||
# all from current batch
|
||||
batch[0] = flt(batch[0]) - qty_to_pop
|
||||
transferred_item_details[(d.voucher_no, d.name)].append([qty_to_pop, batch[1]])
|
||||
transferred_item_details[transferred_item_key].append([qty_to_pop, batch[1]])
|
||||
qty_to_pop = 0
|
||||
|
||||
item_details[key]["qty_after_transaction"] = d.qty_after_transaction
|
||||
|
||||
@@ -6,6 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt
|
||||
from erpnext.stock.utils import update_included_uom_in_report
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
def execute(filters=None):
|
||||
include_uom = filters.get("include_uom")
|
||||
@@ -23,6 +24,7 @@ def execute(filters=None):
|
||||
|
||||
actual_qty = stock_value = 0
|
||||
|
||||
available_serial_nos = {}
|
||||
for sle in sl_entries:
|
||||
item_detail = item_details[sle.item_code]
|
||||
|
||||
@@ -41,6 +43,9 @@ def execute(filters=None):
|
||||
"stock_value": stock_value
|
||||
})
|
||||
|
||||
if sle.serial_no:
|
||||
update_available_serial_nos(available_serial_nos, sle)
|
||||
|
||||
data.append(sle)
|
||||
|
||||
if include_uom:
|
||||
@@ -49,6 +54,27 @@ def execute(filters=None):
|
||||
update_included_uom_in_report(columns, data, include_uom, conversion_factors)
|
||||
return columns, data
|
||||
|
||||
def update_available_serial_nos(available_serial_nos, sle):
|
||||
serial_nos = get_serial_nos(sle.serial_no)
|
||||
key = (sle.item_code, sle.warehouse)
|
||||
if key not in available_serial_nos:
|
||||
available_serial_nos.setdefault(key, [])
|
||||
|
||||
existing_serial_no = available_serial_nos[key]
|
||||
for sn in serial_nos:
|
||||
if sle.actual_qty > 0:
|
||||
if sn in existing_serial_no:
|
||||
existing_serial_no.remove(sn)
|
||||
else:
|
||||
existing_serial_no.append(sn)
|
||||
else:
|
||||
if sn in existing_serial_no:
|
||||
existing_serial_no.remove(sn)
|
||||
else:
|
||||
existing_serial_no.append(sn)
|
||||
|
||||
sle.balance_serial_no = '\n'.join(existing_serial_no)
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
{"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 95},
|
||||
@@ -70,7 +96,8 @@ def get_columns():
|
||||
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
|
||||
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
|
||||
{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
|
||||
{"label": _("Serial #"), "fieldname": "serial_no", "width": 100},
|
||||
{"label": _("Serial No"), "fieldname": "serial_no", "width": 100},
|
||||
{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
|
||||
{"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
|
||||
{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110}
|
||||
]
|
||||
|
||||
@@ -162,10 +162,13 @@ class update_entries_after(object):
|
||||
|
||||
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
|
||||
else:
|
||||
if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
|
||||
# assert
|
||||
if sle.voucher_type=="Stock Reconciliation":
|
||||
if sle.batch_no:
|
||||
self.qty_after_transaction += flt(sle.actual_qty)
|
||||
else:
|
||||
self.qty_after_transaction = sle.qty_after_transaction
|
||||
|
||||
self.valuation_rate = sle.valuation_rate
|
||||
self.qty_after_transaction = sle.qty_after_transaction
|
||||
self.stock_queue = [[self.qty_after_transaction, self.valuation_rate]]
|
||||
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
|
||||
else:
|
||||
|
||||
@@ -3,7 +3,7 @@ frappe
|
||||
gocardless-pro==1.11.0
|
||||
googlemaps==3.1.1
|
||||
pandas==0.24.2
|
||||
plaid-python==3.4.0
|
||||
plaid-python==6.0.0
|
||||
PyGithub==1.44.1
|
||||
python-stdnum==1.12
|
||||
Unidecode==1.1.1
|
||||
|
||||
Reference in New Issue
Block a user