Merge branch 'develop' of https://github.com/frappe/erpnext into loan_refund_jv

This commit is contained in:
Deepesh Garg 2022-02-07 12:49:34 +05:30
commit 8d7331de8f
584 changed files with 18355 additions and 22933 deletions

View File

@ -28,6 +28,7 @@ ignore =
B007,
B950,
W191,
E124, # closing bracket, irritating while writing QB code
max-line-length = 200
exclude=.github/helper/semgrep_rules

View File

@ -8,7 +8,10 @@ sudo apt-get install redis-server libcups2-dev
pip install frappe-bench
git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
frappeuser=${FRAPPE_USER:-"frappe"}
frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site

View File

@ -6,12 +6,23 @@ on:
- '**.js'
- '**.md'
- '**.html'
workflow_dispatch:
push:
branches: [ develop ]
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch:
inputs:
user:
description: 'user'
required: true
default: 'frappe'
type: string
branch:
description: 'Branch name'
default: 'develop'
required: false
type: string
concurrency:
group: server-mariadb-develop-${{ github.event.number }}
@ -95,6 +106,8 @@ jobs:
env:
DB: mariadb
TYPE: server
FRAPPE_USER: ${{ github.event.inputs.user }}
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
- name: Run Tests
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage

View File

@ -254,11 +254,13 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto')
def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): return
account_currency = get_account_currency(item.expense_account)
account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice":
against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account
@ -279,6 +281,10 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if not amount:
return
# check if books nor frozen till endate:
if getdate(end_date) >= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
if via_journal_entry:
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
@ -406,8 +412,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
'account': credit_account,
'credit': base_amount,
'credit_in_account_currency': amount,
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
'party': against,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,
@ -420,8 +424,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
'account': debit_account,
'debit': base_amount,
'debit_in_account_currency': amount,
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
'party': against,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,

View File

@ -7,35 +7,30 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"accounts_transactions_settings_section",
"over_billing_allowance",
"role_allowed_to_over_bill",
"credit_controller",
"make_payment_via_journal_entry",
"column_break_11",
"check_supplier_invoice_uniqueness",
"invoice_and_billing_tab",
"enable_features_section",
"unlink_payment_on_cancellation_of_invoice",
"automatically_fetch_payment_terms",
"delete_linked_ledger_entries",
"book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order",
"column_break_13",
"delete_linked_ledger_entries",
"invoicing_features_section",
"check_supplier_invoice_uniqueness",
"automatically_fetch_payment_terms",
"column_break_17",
"enable_common_party_accounting",
"post_change_gl_entries",
"enable_discount_accounting",
"tax_settings_section",
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"period_closing_settings_section",
"acc_frozen_upto",
"frozen_accounts_modifier",
"column_break_4",
"report_setting_section",
"use_custom_cash_flow",
"deferred_accounting_settings_section",
"book_deferred_entries_based_on",
"column_break_18",
"automatically_process_deferred_accounting_entry",
"book_deferred_entries_via_journal_entry",
"submit_journal_entries",
"tax_settings_section",
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"print_settings",
"show_inclusive_tax_in_print",
"column_break_12",
@ -43,8 +38,25 @@
"currency_exchange_section",
"allow_stale",
"stale_days",
"report_settings_sb",
"use_custom_cash_flow"
"invoicing_settings_tab",
"accounts_transactions_settings_section",
"over_billing_allowance",
"column_break_11",
"role_allowed_to_over_bill",
"credit_controller",
"make_payment_via_journal_entry",
"pos_tab",
"pos_setting_section",
"post_change_gl_entries",
"assets_tab",
"asset_settings_section",
"book_asset_depreciation_entry_automatically",
"closing_settings_tab",
"period_closing_settings_section",
"acc_frozen_upto",
"column_break_25",
"frozen_accounts_modifier",
"report_settings_sb"
],
"fields": [
{
@ -70,10 +82,6 @@
"label": "Determine Address Tax Category From",
"options": "Billing Address\nShipping Address"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "credit_controller",
"fieldtype": "Link",
@ -83,6 +91,7 @@
},
{
"default": "0",
"description": "Enabling ensure each Sales Invoice has a unique value in Supplier Invoice No. field",
"fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness"
@ -168,7 +177,7 @@
"description": "Only select this if you have set up the Cash Flow Mapper documents",
"fieldname": "use_custom_cash_flow",
"fieldtype": "Check",
"label": "Use Custom Cash Flow Format"
"label": "Enable Custom Cash Flow Format"
},
{
"default": "0",
@ -241,7 +250,7 @@
{
"fieldname": "accounts_transactions_settings_section",
"fieldtype": "Section Break",
"label": "Transactions Settings"
"label": "Credit Limit Settings"
},
{
"fieldname": "column_break_11",
@ -272,9 +281,72 @@
},
{
"default": "0",
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
},
{
"fieldname": "enable_features_section",
"fieldtype": "Section Break",
"label": "Invoice Cancellation"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_25",
"fieldtype": "Column Break"
},
{
"fieldname": "asset_settings_section",
"fieldtype": "Section Break",
"label": "Asset Settings"
},
{
"fieldname": "invoicing_settings_tab",
"fieldtype": "Tab Break",
"label": "Credit Limits"
},
{
"fieldname": "assets_tab",
"fieldtype": "Tab Break",
"label": "Assets"
},
{
"fieldname": "closing_settings_tab",
"fieldtype": "Tab Break",
"label": "Accounts Closing"
},
{
"fieldname": "pos_setting_section",
"fieldtype": "Section Break",
"label": "POS Setting"
},
{
"fieldname": "invoice_and_billing_tab",
"fieldtype": "Tab Break",
"label": "Invoice and Billing"
},
{
"fieldname": "invoicing_features_section",
"fieldtype": "Section Break",
"label": "Invoicing Features"
},
{
"fieldname": "column_break_17",
"fieldtype": "Column Break"
},
{
"fieldname": "pos_tab",
"fieldtype": "Tab Break",
"label": "POS"
},
{
"fieldname": "report_setting_section",
"fieldtype": "Section Break",
"label": "Report Setting"
}
],
"icon": "icon-cog",
@ -282,7 +354,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-10-11 17:42:36.427699",
"modified": "2022-02-04 12:32:36.805652",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@ -309,5 +381,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}

View File

@ -16,6 +16,7 @@ from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html
from openpyxl.styles import Font
from openpyxl.utils import get_column_letter
INVALID_VALUES = ("", None)
class BankStatementImport(DataImport):
def __init__(self, *args, **kwargs):
@ -95,6 +96,18 @@ def download_errored_template(data_import_name):
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
data_import.export_errored_rows()
def parse_data_from_template(raw_data):
data = []
for i, row in enumerate(raw_data):
if all(v in INVALID_VALUES for v in row):
# empty row
continue
data.append(row)
return data
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
"""This method runs in background job"""
@ -104,7 +117,8 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
file = import_file_path if import_file_path else google_sheets_url
import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
data = import_file.raw_data
data = parse_data_from_template(import_file.raw_data)
if import_file_path:
add_bank_account(data, bank_account)

View File

@ -2,9 +2,10 @@
# For license information, please see license.txt
from functools import reduce
import frappe
from frappe.utils import flt
from six.moves import reduce
from erpnext.controllers.status_updater import StatusUpdater

View File

@ -15,17 +15,6 @@ frappe.ui.form.on('Cost Center', {
}
}
});
frm.set_query("cost_center", "distributed_cost_center", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
enable_distributed_cost_center: 0,
name: ['!=', frm.doc.name]
}
};
});
},
refresh: function(frm) {
if (!frm.is_new()) {

View File

@ -16,9 +16,6 @@
"cb0",
"is_group",
"disabled",
"section_break_9",
"enable_distributed_cost_center",
"distributed_cost_center",
"lft",
"rgt",
"old_parent"
@ -122,31 +119,13 @@
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"default": "0",
"fieldname": "enable_distributed_cost_center",
"fieldtype": "Check",
"label": "Enable Distributed Cost Center"
},
{
"depends_on": "eval:doc.is_group==0",
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"depends_on": "enable_distributed_cost_center",
"fieldname": "distributed_cost_center",
"fieldtype": "Table",
"label": "Distributed Cost Center",
"options": "Distributed Cost Center"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2020-06-17 16:09:30.025214",
"modified": "2022-01-31 13:22:58.916273",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
@ -189,5 +168,6 @@
"search_fields": "parent_cost_center, is_group",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC"
"sort_order": "ASC",
"states": []
}

View File

@ -4,7 +4,6 @@
import frappe
from frappe import _
from frappe.utils import cint
from frappe.utils.nestedset import NestedSet
from erpnext.accounts.utils import validate_field_number
@ -20,24 +19,6 @@ class CostCenter(NestedSet):
def validate(self):
self.validate_mandatory()
self.validate_parent_cost_center()
self.validate_distributed_cost_center()
def validate_distributed_cost_center(self):
if cint(self.enable_distributed_cost_center):
if not self.distributed_cost_center:
frappe.throw(_("Please enter distributed cost center"))
if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100:
frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100"))
if not self.get('__islocal'):
if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \
and self.check_if_part_of_distributed_cost_center():
frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"))
if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False):
frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center"))
if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)):
frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."))
else:
self.distributed_cost_center = []
def validate_mandatory(self):
if self.cost_center_name != self.company and not self.parent_cost_center:
@ -64,10 +45,10 @@ class CostCenter(NestedSet):
@frappe.whitelist()
def convert_ledger_to_group(self):
if cint(self.enable_distributed_cost_center):
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
if self.check_if_part_of_distributed_cost_center():
frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group"))
if self.if_allocation_exists_against_cost_center():
frappe.throw(_("Cost Center with Allocation records can not be converted to a group"))
if self.check_if_part_of_cost_center_allocation():
frappe.throw(_("Cost Center is a part of Cost Center Allocation, hence cannot be converted to a group"))
if self.check_gle_exists():
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
self.is_group = 1
@ -81,8 +62,17 @@ class CostCenter(NestedSet):
return frappe.db.sql("select name from `tabCost Center` where \
parent_cost_center = %s and docstatus != 2", self.name)
def check_if_part_of_distributed_cost_center(self):
return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name})
def if_allocation_exists_against_cost_center(self):
return frappe.db.get_value("Cost Center Allocation", filters = {
"main_cost_center": self.name,
"docstatus": 1
})
def check_if_part_of_cost_center_allocation(self):
return frappe.db.get_value("Cost Center Allocation Percentage", filters = {
"cost_center": self.name,
"docstatus": 1
})
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
@ -126,8 +116,4 @@ def on_doctype_update():
def get_name_with_number(new_account, account_number):
if account_number and not new_account[0].isdigit():
new_account = account_number + " - " + new_account
return new_account
def check_if_distributed_cost_center_enabled(cost_center_list):
value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
return next((True for x in value_list if x[0]), False)
return new_account

View File

@ -23,33 +23,6 @@ class TestCostCenter(unittest.TestCase):
self.assertRaises(frappe.ValidationError, cost_center.save)
def test_validate_distributed_cost_center(self):
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}):
frappe.get_doc(test_records[0]).insert()
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
frappe.get_doc(test_records[1]).insert()
invalid_distributed_cost_center = frappe.get_doc({
"company": "_Test Company",
"cost_center_name": "_Test Distributed Cost Center",
"doctype": "Cost Center",
"is_group": 0,
"parent_cost_center": "_Test Company - _TC",
"enable_distributed_cost_center": 1,
"distributed_cost_center": [{
"cost_center": "_Test Cost Center - _TC",
"percentage_allocation": 40
}, {
"cost_center": "_Test Cost Center 2 - _TC",
"percentage_allocation": 50
}
]
})
self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save)
def create_cost_center(**args):
args = frappe._dict(args)
if args.cost_center_name:

View File

@ -0,0 +1,19 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cost Center Allocation', {
setup: function(frm) {
let filters = {"is_group": 0};
if (frm.doc.company) {
$.extend(filters, {
"company": frm.doc.company
});
}
frm.set_query('main_cost_center', function() {
return {
filters: filters
};
});
}
});

View File

@ -0,0 +1,128 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "CC-ALLOC-.#####",
"creation": "2022-01-13 20:07:29.871109",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"main_cost_center",
"company",
"column_break_2",
"valid_from",
"section_break_5",
"allocation_percentages",
"amended_from"
],
"fields": [
{
"fieldname": "main_cost_center",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Main Cost Center",
"options": "Cost Center",
"reqd": 1
},
{
"default": "Today",
"fieldname": "valid_from",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Valid From",
"reqd": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"fetch_from": "main_cost_center.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "allocation_percentages",
"fieldtype": "Table",
"label": "Cost Center Allocation Percentages",
"options": "Cost Center Allocation Percentage",
"reqd": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Cost Center Allocation",
"print_hide": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-01-31 11:47:12.086253",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center Allocation",
"name_case": "UPPER CASE",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,90 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import add_days, format_date, getdate
class MainCostCenterCantBeChild(frappe.ValidationError):
pass
class InvalidMainCostCenter(frappe.ValidationError):
pass
class InvalidChildCostCenter(frappe.ValidationError):
pass
class WrongPercentageAllocation(frappe.ValidationError):
pass
class InvalidDateError(frappe.ValidationError):
pass
class CostCenterAllocation(Document):
def validate(self):
self.validate_total_allocation_percentage()
self.validate_from_date_based_on_existing_gle()
self.validate_backdated_allocation()
self.validate_main_cost_center()
self.validate_child_cost_centers()
def validate_total_allocation_percentage(self):
total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
if total_percentage != 100:
frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
def validate_from_date_based_on_existing_gle(self):
# Check if GLE exists against the main cost center
# If exists ensure from date is set after posting date of last GLE
last_gle_date = frappe.db.get_value("GL Entry",
{"cost_center": self.main_cost_center, "is_cancelled": 0},
"posting_date", order_by="posting_date desc")
if last_gle_date:
if getdate(self.valid_from) <= getdate(last_gle_date):
frappe.throw(_("Valid From must be after {0} as last GL Entry against the cost center {1} posted on this date")
.format(last_gle_date, self.main_cost_center), InvalidDateError)
def validate_backdated_allocation(self):
# Check if there are any future existing allocation records against the main cost center
# If exists, warn the user about it
future_allocation = frappe.db.get_value("Cost Center Allocation", filters = {
"main_cost_center": self.main_cost_center,
"valid_from": (">=", self.valid_from),
"name": ("!=", self.name),
"docstatus": 1
}, fieldname=['valid_from', 'name'], order_by='valid_from', as_dict=1)
if future_allocation:
frappe.msgprint(_("Another Cost Center Allocation record {0} applicable from {1}, hence this allocation will be applicable upto {2}")
.format(frappe.bold(future_allocation.name), frappe.bold(format_date(future_allocation.valid_from)),
frappe.bold(format_date(add_days(future_allocation.valid_from, -1)))),
title=_("Warning!"), indicator="orange", alert=1
)
def validate_main_cost_center(self):
# Main cost center itself cannot be entered in child table
if self.main_cost_center in [d.cost_center for d in self.allocation_percentages]:
frappe.throw(_("Main Cost Center {0} cannot be entered in the child table")
.format(self.main_cost_center), MainCostCenterCantBeChild)
# If main cost center is used for allocation under any other cost center,
# allocation cannot be done against it
parent = frappe.db.get_value("Cost Center Allocation Percentage", filters = {
"cost_center": self.main_cost_center,
"docstatus": 1
}, fieldname='parent')
if parent:
frappe.throw(_("{0} cannot be used as a Main Cost Center because it has been used as child in Cost Center Allocation {1}")
.format(self.main_cost_center, parent), InvalidMainCostCenter)
def validate_child_cost_centers(self):
# Check if child cost center is used as main cost center in any existing allocation
main_cost_centers = [d.main_cost_center for d in
frappe.get_all("Cost Center Allocation", {'docstatus': 1}, 'main_cost_center')]
for d in self.allocation_percentages:
if d.cost_center in main_cost_centers:
frappe.throw(_("Cost Center {0} cannot be used for allocation as it is used as main cost center in other allocation record.")
.format(d.cost_center), InvalidChildCostCenter)

View File

@ -0,0 +1,156 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.utils import add_days, today
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation import (
InvalidChildCostCenter,
InvalidDateError,
InvalidMainCostCenter,
MainCostCenterCantBeChild,
WrongPercentageAllocation,
)
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
class TestCostCenterAllocation(unittest.TestCase):
def setUp(self):
cost_centers = ["Main Cost Center 1", "Main Cost Center 2", "Sub Cost Center 1", "Sub Cost Center 2"]
for cc in cost_centers:
create_cost_center(cost_center_name=cc, company="_Test Company")
def test_gle_based_on_cost_center_allocation(self):
cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
{
"Sub Cost Center 1 - _TC": 60,
"Sub Cost Center 2 - _TC": 40
}
)
jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
cost_center = "Main Cost Center 1 - _TC", submit=True)
expected_values = [
["Sub Cost Center 1 - _TC", 0.0, 60],
["Sub Cost Center 2 - _TC", 0.0, 40]
]
gle = frappe.qb.DocType("GL Entry")
gl_entries = (
frappe.qb.from_(gle)
.select(gle.cost_center, gle.debit, gle.credit)
.where(gle.voucher_type == 'Journal Entry')
.where(gle.voucher_no == jv.name)
.where(gle.account == 'Sales - _TC')
.orderby(gle.cost_center)
).run(as_dict=1)
self.assertTrue(gl_entries)
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[i][0], gle.cost_center)
self.assertEqual(expected_values[i][1], gle.debit)
self.assertEqual(expected_values[i][2], gle.credit)
cca.cancel()
jv.cancel()
def test_main_cost_center_cant_be_child(self):
# Main cost center itself cannot be entered in child table
cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
{
"Sub Cost Center 1 - _TC": 60,
"Main Cost Center 1 - _TC": 40
}, save=False
)
self.assertRaises(MainCostCenterCantBeChild, cca.save)
def test_invalid_main_cost_center(self):
# If main cost center is used for allocation under any other cost center,
# allocation cannot be done against it
cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
{
"Sub Cost Center 1 - _TC": 60,
"Sub Cost Center 2 - _TC": 40
}
)
cca2 = create_cost_center_allocation("_Test Company", "Sub Cost Center 1 - _TC",
{
"Sub Cost Center 2 - _TC": 100
}, save=False
)
self.assertRaises(InvalidMainCostCenter, cca2.save)
cca1.cancel()
def test_if_child_cost_center_has_any_allocation_record(self):
# Check if any child cost center is used as main cost center in any other existing allocation
cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
{
"Sub Cost Center 1 - _TC": 60,
"Sub Cost Center 2 - _TC": 40
}
)
cca2 = create_cost_center_allocation("_Test Company", "Main Cost Center 2 - _TC",
{
"Main Cost Center 1 - _TC": 60,
"Sub Cost Center 1 - _TC": 40
}, save=False
)
self.assertRaises(InvalidChildCostCenter, cca2.save)
cca1.cancel()
def test_total_percentage(self):
cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
{
"Sub Cost Center 1 - _TC": 40,
"Sub Cost Center 2 - _TC": 40
}, save=False
)
self.assertRaises(WrongPercentageAllocation, cca.save)
def test_valid_from_based_on_existing_gle(self):
# GLE posted against Sub Cost Center 1 on today
jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
cost_center = "Main Cost Center 1 - _TC", posting_date=today(), submit=True)
# try to set valid from as yesterday
cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
{
"Sub Cost Center 1 - _TC": 60,
"Sub Cost Center 2 - _TC": 40
}, valid_from=add_days(today(), -1), save=False
)
self.assertRaises(InvalidDateError, cca.save)
jv.cancel()
def create_cost_center_allocation(company, main_cost_center, allocation_percentages,
valid_from=None, valid_upto=None, save=True, submit=True):
doc = frappe.new_doc("Cost Center Allocation")
doc.main_cost_center = main_cost_center
doc.company = company
doc.valid_from = valid_from or today()
doc.valid_upto = valid_upto
for cc, percentage in allocation_percentages.items():
doc.append("allocation_percentages", {
"cost_center": cc,
"percentage": percentage
})
if save:
doc.save()
if submit:
doc.submit()
return doc

View File

@ -1,12 +1,13 @@
{
"actions": [],
"creation": "2020-03-19 12:34:01.500390",
"allow_rename": 1,
"creation": "2022-01-13 20:07:30.096306",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cost_center",
"percentage_allocation"
"percentage"
],
"fields": [
{
@ -18,23 +19,23 @@
"reqd": 1
},
{
"fieldname": "percentage_allocation",
"fieldtype": "Float",
"fieldname": "percentage",
"fieldtype": "Percent",
"in_list_view": 1,
"label": "Percentage Allocation",
"label": "Percentage (%)",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-03-19 12:54:43.674655",
"modified": "2022-02-01 22:22:31.589523",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Distributed Cost Center",
"name": "Cost Center Allocation Percentage",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CostCenterAllocationPercentage(Document):
pass

View File

@ -39,9 +39,6 @@ def test_create_test_data():
"selling_cost_center": "Main - _TC",
"income_account": "Sales - _TC"
}],
"show_in_website": 1,
"route":"-test-tesla-car",
"website_warehouse": "Stores - _TC"
})
item.insert()
# create test item price

View File

@ -2,7 +2,7 @@
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"creation": "2018-11-22 22:45:00.370913",
"creation": "2022-01-19 01:09:13.297137",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
@ -10,6 +10,9 @@
"field_order": [
"title",
"company",
"column_break_3",
"disabled",
"section_break_5",
"taxes"
],
"fields": [
@ -36,10 +39,24 @@
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
}
],
"links": [],
"modified": "2021-03-08 19:50:21.416513",
"modified": "2022-01-18 21:11:23.105589",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
@ -82,6 +99,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View File

@ -8,6 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
},
refresh: function(frm) {

View File

@ -407,13 +407,14 @@ class JournalEntry(AccountsController):
debit_or_credit = 'Debit' if d.debit else 'Credit'
party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
debit_or_credit)
against_voucher = ['', against_voucher[1]]
else:
if d.reference_type == "Sales Invoice":
party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
else:
party_account = against_voucher[1]
if (against_voucher[0] != d.party or party_account != d.account):
if (against_voucher[0] != cstr(d.party) or party_account != d.account):
frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
d.reference_type, d.reference_name))
@ -478,13 +479,22 @@ class JournalEntry(AccountsController):
def set_against_account(self):
accounts_debited, accounts_credited = [], []
for d in self.get("accounts"):
if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
for d in self.get('accounts'):
if d.reference_type == 'Sales Invoice':
field = 'customer'
else:
field = 'supplier'
for d in self.get("accounts"):
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
else:
for d in self.get("accounts"):
if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
for d in self.get("accounts"):
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
def validate_debit_credit_amount(self):
for d in self.get('accounts'):

View File

@ -135,7 +135,7 @@ class OpeningInvoiceCreationTool(Document):
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
rate = flt(row.outstanding_amount) / flt(row.qty)
return frappe._dict({
item_dict = frappe._dict({
"uom": default_uom,
"rate": rate or 0.0,
"qty": row.qty,
@ -146,6 +146,13 @@ class OpeningInvoiceCreationTool(Document):
"cost_center": cost_center
})
for dimension in get_accounting_dimensions():
item_dict.update({
dimension: row.get(dimension)
})
return item_dict
item = get_item_dict()
invoice = frappe._dict({
@ -166,7 +173,7 @@ class OpeningInvoiceCreationTool(Document):
accounting_dimension = get_accounting_dimensions()
for dimension in accounting_dimension:
invoice.update({
dimension: item.get(dimension)
dimension: self.get(dimension) or item.get(dimension)
})
return invoice

View File

@ -7,21 +7,26 @@ import frappe
from frappe.cache_manager import clear_doctype_cache
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
disable_dimension,
)
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
get_temporary_opening_account,
)
test_dependencies = ["Customer", "Supplier"]
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
class TestOpeningInvoiceCreationTool(unittest.TestCase):
def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company()
create_dimension()
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None):
doc = frappe.get_single("Opening Invoice Creation Tool")
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
party_1=party_1, party_2=party_2, invoice_number=invoice_number)
party_1=party_1, party_2=party_2, invoice_number=invoice_number, department=department)
doc.update(args)
return doc.make_invoices()
@ -106,6 +111,19 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
doc = frappe.get_doc('Sales Invoice', inv)
doc.cancel()
def test_opening_invoice_with_accounting_dimension(self):
invoices = self.make_invoices(invoice_type="Sales", company="_Test Opening Invoice Company", department='Sales - _TOIC')
expected_value = {
"keys": ["customer", "outstanding_amount", "status", "department"],
0: ["_Test Customer", 300, "Overdue", "Sales - _TOIC"],
1: ["_Test Customer 1", 250, "Overdue", "Sales - _TOIC"],
}
self.check_expected_values(invoices, expected_value, invoice_type="Sales")
def tearDown(self):
disable_dimension()
def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company")

View File

@ -3,6 +3,7 @@
import json
from functools import reduce
import frappe
from frappe import ValidationError, _, scrub, throw
@ -1523,6 +1524,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
if dt in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice']:
pe.project = (doc.get('project') or
reduce(lambda prev,cur: prev or cur, [x.get('project') for x in doc.get('items')], None)) # get first non-empty project from items
if pe.party_type in ["Customer", "Supplier"]:
bank_account = get_party_bank_account(pe.party_type, pe.party)
pe.set("bank_account", bank_account)

View File

@ -291,7 +291,7 @@ class PaymentRequest(Document):
if not status:
return
shopping_cart_settings = frappe.get_doc("Shopping Cart Settings")
shopping_cart_settings = frappe.get_doc("E Commerce Settings")
if status in ["Authorized", "Completed"]:
redirect_to = None
@ -435,13 +435,13 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
""", (ref_dt, ref_dn))
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
def get_gateway_details(args):
def get_gateway_details(args): # nosemgrep
"""return gateway and payment account of default payment gateway"""
if args.get("payment_gateway_account"):
return get_payment_gateway_account(args.get("payment_gateway_account"))
if args.order_type == "Shopping Cart":
payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account
payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account
return get_payment_gateway_account(payment_gateway_account)
gateway_account = get_payment_gateway_account({"is_default": 1})

View File

@ -42,7 +42,6 @@ class POSInvoice(SalesInvoice):
self.validate_serialised_or_batched_item()
self.validate_stock_availablility()
self.validate_return_items_qty()
self.validate_non_stock_items()
self.set_status()
self.set_account_for_mode_of_payment()
self.validate_pos()
@ -158,22 +157,39 @@ class POSInvoice(SalesInvoice):
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
def validate_invalid_serial_nos(self, item):
serial_nos = get_serial_nos(item.serial_no)
error_msg = []
invalid_serials, msg = "", ""
for serial_no in serial_nos:
if not frappe.db.exists('Serial No', serial_no):
invalid_serials = invalid_serials + (", " if invalid_serials else "") + serial_no
msg = (_("Row #{}: Following Serial numbers for item {} are <b>Invalid</b>: {}").format(item.idx, frappe.bold(item.get("item_code")), frappe.bold(invalid_serials)))
if invalid_serials:
error_msg.append(msg)
if error_msg:
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
def validate_stock_availablility(self):
if self.is_return or self.docstatus != 1:
return
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
for d in self.get('items'):
is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_item'))
if is_service_item:
return
if d.serial_no:
self.validate_pos_reserved_serial_nos(d)
self.validate_delivered_serial_nos(d)
self.validate_invalid_serial_nos(d)
elif d.batch_no:
self.validate_pos_reserved_batch_qty(d)
else:
if allow_negative_stock:
return
available_stock = get_stock_availability(d.item_code, d.warehouse)
available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse)
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
if flt(available_stock) <= 0:
@ -244,14 +260,6 @@ class POSInvoice(SalesInvoice):
.format(d.idx, bold_serial_no, bold_return_against)
)
def validate_non_stock_items(self):
for d in self.get("items"):
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
if not is_stock_item:
if not frappe.db.exists('Product Bundle', d.item_code):
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.")
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
def validate_mode_of_payment(self):
if len(self.payments) == 0:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
@ -491,12 +499,18 @@ class POSInvoice(SalesInvoice):
@frappe.whitelist()
def get_stock_availability(item_code, warehouse):
if frappe.db.get_value('Item', item_code, 'is_stock_item'):
is_stock_item = True
bin_qty = get_bin_qty(item_code, warehouse)
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
return bin_qty - pos_sales_qty
return bin_qty - pos_sales_qty, is_stock_item
else:
is_stock_item = False
if frappe.db.exists('Product Bundle', item_code):
return get_bundle_availability(item_code, warehouse)
return get_bundle_availability(item_code, warehouse), is_stock_item
else:
# Is a service item
return 0, is_stock_item
def get_bundle_availability(bundle_item_code, warehouse):
product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)

View File

@ -354,6 +354,24 @@ class TestPOSInvoice(unittest.TestCase):
pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
def test_invalid_serial_no_validation(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item(company='_Test Company',
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
serial_nos = se.get("items")[0].serial_no + 'wrong'
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
item=se.get("items")[0].item_code, rate=1000, qty=2, do_not_save=1)
pos.get('items')[0].has_serial_no = 1
pos.get('items')[0].serial_no = serial_nos
pos.insert()
self.assertRaises(frappe.ValidationError, pos.submit)
def test_loyalty_points(self):
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,

View File

@ -628,6 +628,26 @@ class TestPricingRule(unittest.TestCase):
for doc in [si, si1]:
doc.delete()
def test_multiple_pricing_rules_with_min_qty(self):
make_pricing_rule(discount_percentage=20, selling=1, priority=1, min_qty=4,
apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 1")
make_pricing_rule(discount_percentage=10, selling=1, priority=2, min_qty=4,
apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 2")
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD")
item = si.items[0]
item.stock_qty = 1
si.save()
self.assertFalse(item.discount_percentage)
item.qty = 5
item.stock_qty = 5
si.save()
self.assertEqual(item.discount_percentage, 30)
si.delete()
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2")
test_dependencies = ["Campaign"]
def make_pricing_rule(**args):

View File

@ -73,7 +73,7 @@ def sorted_by_priority(pricing_rules, args, doc=None):
for key in sorted(pricing_rule_dict):
pricing_rules_list.extend(pricing_rule_dict.get(key))
return pricing_rules_list or pricing_rules
return pricing_rules_list
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = []

View File

@ -537,8 +537,11 @@ class PurchaseInvoice(BuyingController):
voucher_wise_stock_value = {}
if self.update_stock:
for d in frappe.get_all('Stock Ledger Entry',
fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
stock_ledger_entries = frappe.get_all("Stock Ledger Entry",
fields = ["voucher_detail_no", "stock_value_difference", "warehouse"],
filters={"voucher_no": self.name, "voucher_type": self.doctype, "is_cancelled": 0}
)
for d in stock_ledger_entries:
voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
@ -548,6 +551,10 @@ class PurchaseInvoice(BuyingController):
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
provisional_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, \
'enable_provisional_accounting_for_non_stock_items'))
purchase_receipt_doc_map = {}
for item in self.get("items"):
if flt(item.base_net_amount):
@ -643,19 +650,23 @@ class PurchaseInvoice(BuyingController):
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
if auto_accounting_for_non_stock_items:
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:
provisional_account = self.get_company_default("default_provisional_account")
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
if not purchase_receipt_doc:
purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt)
purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0,
'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail,
'account':service_received_but_not_billed_account}, ['name'])
'account':provisional_account}, ['name'])
if expense_booked_in_pr:
expense_account = service_received_but_not_billed_account
# Intentionally passing purchase invoice item to handle partial billing
purchase_receipt_doc.add_provisional_gl_entry(item, gl_entries, self.posting_date, reverse=1)
if not self.is_internal_transfer():
gl_entries.append(self.get_gl_dict({

View File

@ -11,12 +11,17 @@ from frappe.utils import add_days, cint, flt, getdate, nowdate, today
import erpnext
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.controllers.buying_controller import QtyMismatchError
from erpnext.exceptions import InvalidCurrency
from erpnext.projects.doctype.project.test_project import make_project
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as create_purchase_invoice_from_receipt,
)
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
get_taxes,
make_purchase_receipt,
@ -1147,8 +1152,6 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_purchase_invoice_advance_taxes(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
# create a new supplier to test
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
@ -1221,6 +1224,45 @@ class TestPurchaseInvoice(unittest.TestCase):
payment_entry.load_from_db()
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
def test_provisional_accounting_entry(self):
item = create_item("_Test Non Stock Item", is_stock_item=0)
provisional_account = create_account(account_name="Provision Account",
parent_account="Current Liabilities - _TC", company="_Test Company")
company = frappe.get_doc('Company', '_Test Company')
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()
pr = make_purchase_receipt(item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2))
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.items[0].expense_account = 'Cost of Goods Sold - _TC'
pi.save()
pi.submit()
# Check GLE for Purchase Invoice
expected_gle = [
['Cost of Goods Sold - _TC', 250, 0, add_days(pr.posting_date, -1)],
['Creditors - _TC', 0, 250, add_days(pr.posting_date, -1)]
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 250, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 250, pr.posting_date],
["Provision Account - _TC", 0, 250, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date]
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
company.enable_provisional_accounting_for_non_stock_items = 0
company.save()
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry`

View File

@ -1,6 +1,8 @@
{% include "erpnext/regional/india/taxes.js" %}
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
erpnext.setup_auto_gst_taxation('Sales Invoice');
erpnext.setup_einvoice_actions('Sales Invoice')
frappe.ui.form.on("Sales Invoice", {
setup: function(frm) {

View File

@ -36,4 +36,139 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
};
list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
const generate_irns = () => {
const docnames = list_view.get_checked_items(true);
if (docnames && docnames.length) {
frappe.call({
method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
args: { docnames },
freeze: true,
freeze_message: __('Generating E-Invoices...')
});
} else {
frappe.msgprint({
message: __('Please select at least one sales invoice to generate IRN'),
title: __('No Invoice Selected'),
indicator: 'red'
});
}
};
const cancel_irns = () => {
const docnames = list_view.get_checked_items(true);
const fields = [
{
"label": "Reason",
"fieldname": "reason",
"fieldtype": "Select",
"reqd": 1,
"default": "1-Duplicate",
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
},
{
"label": "Remark",
"fieldname": "remark",
"fieldtype": "Data",
"reqd": 1
}
];
const d = new frappe.ui.Dialog({
title: __("Cancel IRN"),
fields: fields,
primary_action: function() {
const data = d.get_values();
frappe.call({
method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
args: {
doctype: list_view.doctype,
docnames,
reason: data.reason.split('-')[0],
remark: data.remark
},
freeze: true,
freeze_message: __('Cancelling E-Invoices...'),
});
d.hide();
},
primary_action_label: __('Submit')
});
d.show();
};
let einvoicing_enabled = false;
frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
einvoicing_enabled = enabled;
});
list_view.$result.on("change", "input[type=checkbox]", () => {
if (einvoicing_enabled) {
const docnames = list_view.get_checked_items(true);
// show/hide e-invoicing actions when no sales invoices are checked
if (docnames && docnames.length) {
// prevent adding actions twice if e-invoicing action group already exists
if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
}
} else {
list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
}
}
});
frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
const { failures, user, invoices } = data;
if (invoices.length != failures.length) {
frappe.msgprint({
message: __('{0} e-invoices generated successfully', [invoices.length]),
title: __('Bulk E-Invoice Generation Complete'),
indicator: 'orange'
});
}
if (failures && failures.length && user == frappe.session.user) {
let message = `
Failed to generate IRNs for following ${failures.length} sales invoices:
<ul style="padding-left: 20px; padding-top: 5px;">
${failures.map(d => `<li>${d.docname}</li>`).join('')}
</ul>
`;
frappe.msgprint({
message: message,
title: __('Bulk E-Invoice Generation Complete'),
indicator: 'orange'
});
}
});
frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
const { failures, user, invoices } = data;
if (invoices.length != failures.length) {
frappe.msgprint({
message: __('{0} e-invoices cancelled successfully', [invoices.length]),
title: __('Bulk E-Invoice Cancellation Complete'),
indicator: 'orange'
});
}
if (failures && failures.length && user == frappe.session.user) {
let message = `
Failed to cancel IRNs for following ${failures.length} sales invoices:
<ul style="padding-left: 20px; padding-top: 5px;">
${failures.map(d => `<li>${d.docname}</li>`).join('')}
</ul>
`;
frappe.msgprint({
message: message,
title: __('Bulk E-Invoice Cancellation Complete'),
indicator: 'orange'
});
}
});
};

View File

@ -469,7 +469,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
let row = frappe.get_doc(d.doctype, d.name)
set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail)
});
frm.trigger("calculate_timesheet_totals");
this.frm.trigger("calculate_timesheet_totals");
}
}
};

View File

@ -43,6 +43,7 @@ from erpnext.setup.doctype.company.company import update_company_current_month_s
from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
from erpnext.stock.utils import calculate_mapped_packed_items_return
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@ -293,6 +294,8 @@ class SalesInvoice(SellingController):
def before_cancel(self):
self.check_if_consolidated_invoice()
super(SalesInvoice, self).before_cancel()
self.update_time_sheet(None)
def on_cancel(self):
@ -728,8 +731,11 @@ class SalesInvoice(SellingController):
def update_packing_list(self):
if cint(self.update_stock) == 1:
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self)
if cint(self.is_return) and self.return_against:
calculate_mapped_packed_items_return(self)
else:
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self)
else:
self.set('packed_items', [])

View File

@ -1781,47 +1781,6 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
def test_deferred_revenue_post_account_freeze_upto_by_admin(self):
frappe.set_user("Administrator")
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_revenue = 1
item.deferred_revenue_account = deferred_account
item.no_of_months = 12
item.save()
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager')
pda1 = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=nowdate(),
start_date="2019-01-01",
end_date="2019-03-31",
type="Income",
company="_Test Company"
))
pda1.insert()
self.assertRaises(frappe.ValidationError, pda1.submit)
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
def test_fixed_deferred_revenue(self):
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
@ -2141,6 +2100,54 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
self.assertEqual(data['billLists'][0]['fromStateCode'],27)
def test_einvoice_submission_without_irn(self):
# init
einvoice_settings = frappe.get_doc('E Invoice Settings')
einvoice_settings.enable = 1
einvoice_settings.applicable_from = nowdate()
einvoice_settings.append('credentials', {
'company': '_Test Company',
'gstin': '27AAECE4835E1ZR',
'username': 'test',
'password': 'test'
})
einvoice_settings.save()
country = frappe.flags.country
frappe.flags.country = 'India'
si = make_sales_invoice_for_ewaybill()
self.assertRaises(frappe.ValidationError, si.submit)
si.irn = 'test_irn'
si.submit()
# reset
einvoice_settings = frappe.get_doc('E Invoice Settings')
einvoice_settings.enable = 0
frappe.flags.country = country
def test_einvoice_json(self):
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
si = get_sales_invoice_for_e_invoice()
si.discount_amount = 100
si.save()
einvoice = make_einvoice(si)
self.assertTrue(einvoice['EwbDtls'])
validate_totals(einvoice)
si.apply_discount_on = 'Net Total'
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
[d.set('included_in_print_rate', 1) for d in si.taxes]
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
def test_item_tax_net_range(self):
item = create_item("T Shirt")
@ -2233,9 +2240,9 @@ class TestSalesInvoice(unittest.TestCase):
asset.load_from_db()
expected_values = [
["2020-06-30", 1311.48, 1311.48],
["2021-06-30", 20000.0, 21311.48],
["2021-09-30", 5041.1, 26352.58]
["2020-06-30", 1366.12, 1366.12],
["2021-06-30", 20000.0, 21366.12],
["2021-09-30", 5041.1, 26407.22]
]
for i, schedule in enumerate(asset.schedules):
@ -2283,12 +2290,12 @@ class TestSalesInvoice(unittest.TestCase):
asset.load_from_db()
expected_values = [
["2020-06-30", 1311.48, 1311.48, True],
["2021-06-30", 20000.0, 21311.48, True],
["2022-06-30", 20000.0, 41311.48, False],
["2023-06-30", 20000.0, 61311.48, False],
["2024-06-30", 20000.0, 81311.48, False],
["2025-06-06", 18688.52, 100000.0, False]
["2020-06-30", 1366.12, 1366.12, True],
["2021-06-30", 20000.0, 21366.12, True],
["2022-06-30", 20000.0, 41366.12, False],
["2023-06-30", 20000.0, 61366.12, False],
["2024-06-30", 20000.0, 81366.12, False],
["2025-06-06", 18633.88, 100000.0, False]
]
for i, schedule in enumerate(asset.schedules):
@ -2482,6 +2489,74 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
def test_multi_currency_deferred_revenue_via_journal_entry(self):
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
acc_settings = frappe.get_single('Accounts Settings')
acc_settings.book_deferred_entries_via_journal_entry = 1
acc_settings.submit_journal_entries = 1
acc_settings.save()
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_expense = 1
item.deferred_revenue_account = deferred_account
item.save()
si = create_sales_invoice(customer='_Test Customer USD', currency='USD',
item=item.name, qty=1, rate=100, conversion_rate=60, do_not_save=True)
si.set_posting_time = 1
si.posting_date = '2019-01-01'
si.debit_to = '_Test Receivable USD - _TC'
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-01"
si.items[0].service_end_date = "2019-03-30"
si.items[0].deferred_expense_account = deferred_account
si.save()
si.submit()
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
pda1 = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=nowdate(),
start_date="2019-01-01",
end_date="2019-03-31",
type="Income",
company="_Test Company"
))
pda1.insert()
pda1.submit()
expected_gle = [
["Sales - _TC", 0.0, 2089.89, "2019-01-28"],
[deferred_account, 2089.89, 0.0, "2019-01-28"],
["Sales - _TC", 0.0, 1887.64, "2019-02-28"],
[deferred_account, 1887.64, 0.0, "2019-02-28"],
["Sales - _TC", 0.0, 2022.47, "2019-03-15"],
[deferred_account, 2022.47, 0.0, "2019-03-15"]
]
gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
order by posting_date asc, account asc""", (si.items[0].name, si.posting_date), as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.credit)
self.assertEqual(expected_gle[i][2], gle.debit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
acc_settings = frappe.get_single('Accounts Settings')
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'

View File

@ -71,7 +71,8 @@ class ShippingRule(Document):
if doc.currency != doc.company_currency:
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
if shipping_amount:
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"):

View File

@ -2,12 +2,13 @@
"actions": [],
"allow_rename": 1,
"autoname": "field:title",
"creation": "2018-11-22 23:38:39.668804",
"creation": "2022-01-19 01:09:28.920486",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title"
"title",
"disabled"
],
"fields": [
{
@ -18,14 +19,21 @@
"label": "Title",
"reqd": 1,
"unique": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-03-03 11:50:38.748872",
"modified": "2022-01-18 21:13:41.161017",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Category",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@ -65,5 +73,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@ -98,7 +98,7 @@ class TaxRule(Document):
def validate_use_for_shopping_cart(self):
'''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one'''
if (not self.use_for_shopping_cart
and cint(frappe.db.get_single_value('Shopping Cart Settings', 'enabled'))
and cint(frappe.db.get_single_value('E Commerce Settings', 'enabled'))
and not frappe.db.get_value('Tax Rule', {'use_for_shopping_cart': 1, 'name': ['!=', self.name]})):
self.use_for_shopping_cart = 1

View File

@ -28,14 +28,14 @@
{
"columns": 2,
"fieldname": "single_threshold",
"fieldtype": "Currency",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Single Transaction Threshold"
},
{
"columns": 3,
"fieldname": "cumulative_threshold",
"fieldtype": "Currency",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Cumulative Transaction Threshold"
},
@ -59,7 +59,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-08-31 11:42:12.213977",
"modified": "2022-01-13 12:04:42.904263",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Rate",
@ -68,5 +68,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@ -2,15 +2,17 @@
"creation": "2021-08-24 12:28:18.044902",
"docstatus": 0,
"doctype": "Form Tour",
"first_document": 0,
"idx": 0,
"include_name_field": 0,
"is_standard": 1,
"modified": "2021-08-24 12:28:18.044902",
"modified": "2022-01-18 18:32:17.102330",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges Template",
"owner": "Administrator",
"reference_doctype": "Sales Taxes and Charges Template",
"save_on_complete": 0,
"save_on_complete": 1,
"steps": [
{
"description": "A name by which you will identify this template. You can change this later.",

View File

@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import copy
import frappe
from frappe import _
from frappe.model.meta import get_field_precision
@ -51,49 +53,57 @@ def validate_accounting_period(gl_map):
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
def process_gl_map(gl_map, merge_entries=True, precision=None):
if not gl_map:
return []
gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
if merge_entries:
gl_map = merge_similar_entries(gl_map, precision)
for entry in gl_map:
# toggle debit, credit if negative entry
if flt(entry.debit) < 0:
entry.credit = flt(entry.credit) - flt(entry.debit)
entry.debit = 0.0
if flt(entry.debit_in_account_currency) < 0:
entry.credit_in_account_currency = \
flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
entry.debit_in_account_currency = 0.0
if flt(entry.credit) < 0:
entry.debit = flt(entry.debit) - flt(entry.credit)
entry.credit = 0.0
if flt(entry.credit_in_account_currency) < 0:
entry.debit_in_account_currency = \
flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
entry.credit_in_account_currency = 0.0
update_net_values(entry)
gl_map = toggle_debit_credit_if_negative(gl_map)
return gl_map
def update_net_values(entry):
# In some scenarios net value needs to be shown in the ledger
# This method updates net values as debit or credit
if entry.post_net_value and entry.debit and entry.credit:
if entry.debit > entry.credit:
entry.debit = entry.debit - entry.credit
entry.debit_in_account_currency = entry.debit_in_account_currency \
- entry.credit_in_account_currency
entry.credit = 0
entry.credit_in_account_currency = 0
else:
entry.credit = entry.credit - entry.debit
entry.credit_in_account_currency = entry.credit_in_account_currency \
- entry.debit_in_account_currency
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
cost_center_allocation = get_cost_center_allocation_data(gl_map[0]["company"], gl_map[0]["posting_date"])
if not cost_center_allocation:
return gl_map
entry.debit = 0
entry.debit_in_account_currency = 0
new_gl_map = []
for d in gl_map:
cost_center = d.get("cost_center")
if cost_center and cost_center_allocation.get(cost_center):
for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
gle = copy.deepcopy(d)
gle.cost_center = sub_cost_center
for field in ("debit", "credit", "debit_in_account_currency", "credit_in_company_currency"):
gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
new_gl_map.append(gle)
else:
new_gl_map.append(d)
return new_gl_map
def get_cost_center_allocation_data(company, posting_date):
par = frappe.qb.DocType("Cost Center Allocation")
child = frappe.qb.DocType("Cost Center Allocation Percentage")
records = (
frappe.qb.from_(par).inner_join(child).on(par.name == child.parent)
.select(par.main_cost_center, child.cost_center, child.percentage)
.where(par.docstatus == 1)
.where(par.company == company)
.where(par.valid_from <= posting_date)
.orderby(par.valid_from, order=frappe.qb.desc)
).run(as_dict=True)
cc_allocation = frappe._dict()
for d in records:
cc_allocation.setdefault(d.main_cost_center, frappe._dict())\
.setdefault(d.cost_center, d.percentage)
return cc_allocation
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
@ -145,6 +155,49 @@ def check_if_in_list(gle, gl_map, dimensions=None):
if same_head:
return e
def toggle_debit_credit_if_negative(gl_map):
for entry in gl_map:
# toggle debit, credit if negative entry
if flt(entry.debit) < 0:
entry.credit = flt(entry.credit) - flt(entry.debit)
entry.debit = 0.0
if flt(entry.debit_in_account_currency) < 0:
entry.credit_in_account_currency = \
flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
entry.debit_in_account_currency = 0.0
if flt(entry.credit) < 0:
entry.debit = flt(entry.debit) - flt(entry.credit)
entry.credit = 0.0
if flt(entry.credit_in_account_currency) < 0:
entry.debit_in_account_currency = \
flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
entry.credit_in_account_currency = 0.0
update_net_values(entry)
return gl_map
def update_net_values(entry):
# In some scenarios net value needs to be shown in the ledger
# This method updates net values as debit or credit
if entry.post_net_value and entry.debit and entry.credit:
if entry.debit > entry.credit:
entry.debit = entry.debit - entry.credit
entry.debit_in_account_currency = entry.debit_in_account_currency \
- entry.credit_in_account_currency
entry.credit = 0
entry.credit_in_account_currency = 0
else:
entry.credit = entry.credit - entry.debit
entry.credit_in_account_currency = entry.credit_in_account_currency \
- entry.debit_in_account_currency
entry.debit = 0
entry.debit_in_account_currency = 0
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_cwip_accounts(gl_map)

View File

@ -13,15 +13,12 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
"idx": 0,
"is_complete": 0,
"modified": "2021-08-13 11:59:35.690443",
"modified": "2022-01-18 18:35:52.326688",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",
"owner": "Administrator",
"steps": [
{
"step": "Company"
},
{
"step": "Chart of Accounts"
},

View File

@ -1,22 +0,0 @@
{
"action": "Go to Page",
"action_label": "Let's Review your Company",
"creation": "2021-06-29 14:47:42.497318",
"description": "# Company\n\nIn ERPNext, you can also create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company. \n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-08-13 11:43:35.767341",
"modified_by": "Administrator",
"name": "Company",
"owner": "Administrator",
"path": "app/company",
"reference_document": "Company",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Review Company",
"validate_action": 1
}

View File

@ -58,7 +58,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
party = frappe.get_doc(party_type, party)
currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
currency = party.get("default_currency") or currency or get_company_currency(company)
party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
set_contact_details(party_details, party, party_type)

View File

@ -0,0 +1,173 @@
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
<div class="page-break">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
<div class="print-heading">
<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
</div>
</div>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
<h5 class="font-bold" style="margin-top: 0px;">1. Transaction Details</h5>
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
<div class="col-xs-8 column-break">
<div class="row data-field">
<div class="col-xs-4"><label>IRN</label></div>
<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Ack. No</label></div>
<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Ack. Date</label></div>
<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Category</label></div>
<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Document Type</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Document No</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
</div>
</div>
<div class="col-xs-4 column-break">
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
</div>
</div>
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">2. Party Details</h5>
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
{%- set seller = einvoice.SellerDtls -%}
<div class="col-xs-6 column-break">
<h5 style="margin-bottom: 5px;">Seller</h5>
<p>{{ seller.Gstin }}</p>
<p>{{ seller.LglNm }}</p>
<p>{{ seller.Addr1 }}</p>
{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
<p>{{ seller.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
{%- if einvoice.ShipDtls -%}
{%- set shipping = einvoice.ShipDtls -%}
<h5 style="margin-bottom: 5px;">Shipped From</h5>
<p>{{ shipping.Gstin }}</p>
<p>{{ shipping.LglNm }}</p>
<p>{{ shipping.Addr1 }}</p>
{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
<p>{{ shipping.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
{% endif %}
</div>
{%- set buyer = einvoice.BuyerDtls -%}
<div class="col-xs-6 column-break">
<h5 style="margin-bottom: 5px;">Buyer</h5>
<p>{{ buyer.Gstin }}</p>
<p>{{ buyer.LglNm }}</p>
<p>{{ buyer.Addr1 }}</p>
{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
<p>{{ buyer.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
{%- if einvoice.DispDtls -%}
{%- set dispatch = einvoice.DispDtls -%}
<h5 style="margin-bottom: 5px;">Dispatched From</h5>
{%- if dispatch.Gstin -%} <p>{{ dispatch.Gstin }}</p> {% endif %}
<p>{{ dispatch.LglNm }}</p>
<p>{{ dispatch.Addr1 }}</p>
{%- if dispatch.Addr2 -%} <p>{{ dispatch.Addr2 }}</p> {% endif %}
<p>{{ dispatch.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.dispatch_address_name, "gst_state") }} - {{ dispatch.Pin }}</p>
{% endif %}
</div>
</div>
<div style="overflow-x: auto;">
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">3. Item Details</h5>
<table class="table table-bordered">
<thead>
<tr>
<th class="text-left" style="width: 3%;">Sr. No.</th>
<th class="text-left">Item</th>
<th class="text-left" style="width: 10%;">HSN Code</th>
<th class="text-left" style="width: 5%;">Qty</th>
<th class="text-left" style="width: 5%;">UOM</th>
<th class="text-left">Rate</th>
<th class="text-left" style="width: 5%;">Discount</th>
<th class="text-left">Taxable Amount</th>
<th class="text-left" style="width: 7%;">Tax Rate</th>
<th class="text-left" style="width: 5%;">Other Charges</th>
<th class="text-left">Total</th>
</tr>
</thead>
<tbody>
{% for item in einvoice.ItemList %}
<tr>
<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
<td class="text-left">{{ item.PrdDesc }}</td>
<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="overflow-x: auto;">
<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
<table class="table table-bordered">
<thead>
<tr>
<th class="text-left">Taxable Amount</th>
<th class="text-left">CGST</th>
<th class="text-left"">SGST</th>
<th class="text-left">IGST</th>
<th class="text-left">CESS</th>
<th class="text-left" style="width: 10%;">State CESS</th>
<th class="text-left">Discount</th>
<th class="text-left" style="width: 10%;">Other Charges</th>
<th class="text-left" style="width: 10%;">Round Off</th>
<th class="text-left">Total Value</th>
</tr>
</thead>
<tbody>
{%- set value_details = einvoice.ValDtls -%}
<tr>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,24 @@
{
"align_labels_right": 1,
"creation": "2020-10-10 18:01:21.032914",
"custom_format": 0,
"default_print_language": "en-US",
"disabled": 1,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "",
"idx": 0,
"line_breaks": 1,
"modified": "2020-10-23 19:54:40.634936",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GST E-Invoice",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 1,
"standard": "Yes"
}

View File

@ -5,7 +5,6 @@
import frappe
from frappe import _, scrub
from frappe.utils import cint, flt
from six import iteritems
from erpnext.accounts.party import get_partywise_advanced_payment_amount
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
@ -40,7 +39,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
if self.filters.show_gl_balance:
gl_balance_map = get_gl_balance(self.filters.report_date)
for party, party_dict in iteritems(self.party_total):
for party, party_dict in self.party_total.items():
if party_dict.outstanding == 0:
continue

View File

@ -120,11 +120,11 @@ def check_opening_balance(asset, liability, equity):
opening_balance = 0
float_precision = cint(frappe.db.get_default("float_precision")) or 2
if asset:
opening_balance = flt(asset[0].get("opening_balance", 0), float_precision)
opening_balance = flt(asset[-1].get("opening_balance", 0), float_precision)
if liability:
opening_balance -= flt(liability[0].get("opening_balance", 0), float_precision)
opening_balance -= flt(liability[-1].get("opening_balance", 0), float_precision)
if equity:
opening_balance -= flt(equity[0].get("opening_balance", 0), float_precision)
opening_balance -= flt(equity[-1].get("opening_balance", 0), float_precision)
opening_balance = flt(opening_balance, float_precision)
if opening_balance:

View File

@ -29,18 +29,6 @@ def execute(filters=None):
dimension_items = cam_map.get(dimension)
if dimension_items:
data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0)
else:
DCC_allocation = frappe.db.sql('''SELECT parent, sum(percentage_allocation) as percentage_allocation
FROM `tabDistributed Cost Center`
WHERE cost_center IN %(dimension)s
AND parent NOT IN %(dimension)s
GROUP BY parent''',{'dimension':[dimension]})
if DCC_allocation:
filters['budget_against_filter'] = [DCC_allocation[0][0]]
ddc_cam_map = get_dimension_account_month_map(filters)
dimension_items = ddc_cam_map.get(DCC_allocation[0][0])
if dimension_items:
data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1])
chart = get_chart_data(filters, columns, data)

View File

@ -17,10 +17,42 @@ from erpnext.stock.doctype.item.test_item import create_item
class TestDeferredRevenueAndExpense(unittest.TestCase):
@classmethod
def setUpClass(self):
clear_old_entries()
clear_accounts_and_items()
create_company()
self.maxDiff = None
def clear_old_entries(self):
sinv = qb.DocType("Sales Invoice")
sinv_item = qb.DocType("Sales Invoice Item")
pinv = qb.DocType("Purchase Invoice")
pinv_item = qb.DocType("Purchase Invoice Item")
# delete existing invoices with deferred items
deferred_invoices = (
qb.from_(sinv)
.join(sinv_item)
.on(sinv.name == sinv_item.parent)
.select(sinv.name)
.where(sinv_item.enable_deferred_revenue == 1)
.run()
)
if deferred_invoices:
qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
deferred_invoices = (
qb.from_(pinv)
.join(pinv_item)
.on(pinv.name == pinv_item.parent)
.select(pinv.name)
.where(pinv_item.enable_deferred_expense == 1)
.run()
)
if deferred_invoices:
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
def test_deferred_revenue(self):
self.clear_old_entries()
# created deferred expense accounts, if not found
deferred_revenue_account = create_account(
account_name="Deferred Revenue",
@ -108,6 +140,8 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
self.assertEqual(report.period_total, expected)
def test_deferred_expense(self):
self.clear_old_entries()
# created deferred expense accounts, if not found
deferred_expense_account = create_account(
account_name="Deferred Expense",
@ -198,6 +232,91 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
]
self.assertEqual(report.period_total, expected)
def test_zero_months(self):
self.clear_old_entries()
# created deferred expense accounts, if not found
deferred_revenue_account = create_account(
account_name="Deferred Revenue",
parent_account="Current Liabilities - _CD",
company="_Test Company DR",
)
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
acc_settings.book_deferred_entries_based_on = "Months"
acc_settings.save()
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test Customer DR"
customer.type = "Individual"
customer.insert()
item = create_item(
"_Test Internet Subscription",
is_stock_item=0,
warehouse="All Warehouses - _CD",
company="_Test Company DR",
)
item.enable_deferred_revenue = 1
item.deferred_revenue_account = deferred_revenue_account
item.no_of_months = 0
item.save()
si = create_sales_invoice(
item=item.name,
company="_Test Company DR",
customer="_Test Customer DR",
debit_to="Debtors - _CD",
posting_date="2021-05-01",
parent_cost_center="Main - _CD",
cost_center="Main - _CD",
do_not_submit=True,
rate=300,
price_list_rate=300,
)
si.items[0].enable_deferred_revenue = 1
si.items[0].deferred_revenue_account = deferred_revenue_account
si.items[0].income_account = "Sales - _CD"
si.save()
si.submit()
pda = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
posting_date=nowdate(),
start_date="2021-05-01",
end_date="2021-08-01",
type="Income",
company="_Test Company DR",
)
)
pda.insert()
pda.submit()
# execute report
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
self.filters = frappe._dict(
{
"company": frappe.defaults.get_user_default("Company"),
"filter_based_on": "Date Range",
"period_start_date": "2021-05-01",
"period_end_date": "2021-08-01",
"from_fiscal_year": fiscal_year.year,
"to_fiscal_year": fiscal_year.year,
"periodicity": "Monthly",
"type": "Revenue",
"with_upcoming_postings": False,
}
)
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
report.run()
expected = [
{"key": "may_2021", "total": 300.0, "actual": 300.0},
{"key": "jun_2021", "total": 0, "actual": 0},
{"key": "jul_2021", "total": 0, "actual": 0},
{"key": "aug_2021", "total": 0, "actual": 0},
]
self.assertEqual(report.period_total, expected)
def create_company():
company = frappe.db.exists("Company", "_Test Company DR")
@ -209,15 +328,11 @@ def create_company():
company.insert()
def clear_old_entries():
def clear_accounts_and_items():
item = qb.DocType("Item")
account = qb.DocType("Account")
customer = qb.DocType("Customer")
supplier = qb.DocType("Supplier")
sinv = qb.DocType("Sales Invoice")
sinv_item = qb.DocType("Sales Invoice Item")
pinv = qb.DocType("Purchase Invoice")
pinv_item = qb.DocType("Purchase Invoice Item")
qb.from_(account).delete().where(
(account.account_name == "Deferred Revenue")
@ -228,26 +343,3 @@ def clear_old_entries():
).run()
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
# delete existing invoices with deferred items
deferred_invoices = (
qb.from_(sinv)
.join(sinv_item)
.on(sinv.name == sinv_item.parent)
.select(sinv.name)
.where(sinv_item.enable_deferred_revenue == 1)
.run()
)
if deferred_invoices:
qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
deferred_invoices = (
qb.from_(pinv)
.join(pinv_item)
.on(pinv.name == pinv_item.parent)
.select(pinv.name)
.where(pinv_item.enable_deferred_expense == 1)
.run()
)
if deferred_invoices:
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()

View File

@ -282,7 +282,8 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency
total_row = {
"account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"currency": company_currency
"currency": company_currency,
"opening_balance": 0.0
}
for row in out:
@ -294,6 +295,7 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency
total_row.setdefault("total", 0.0)
total_row["total"] += flt(row["total"])
total_row["opening_balance"] += row["opening_balance"]
row["total"] = ""
if "total" in total_row:
@ -387,42 +389,15 @@ def set_gl_entries_by_account(
key: value
})
distributed_cost_center_query = ""
if filters and filters.get('cost_center'):
distributed_cost_center_query = """
UNION ALL
SELECT posting_date,
account,
debit*(DCC_allocation.percentage_allocation/100) as debit,
credit*(DCC_allocation.percentage_allocation/100) as credit,
is_opening,
fiscal_year,
debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency,
account_currency
FROM `tabGL Entry`,
(
SELECT parent, sum(percentage_allocation) as percentage_allocation
FROM `tabDistributed Cost Center`
WHERE cost_center IN %(cost_center)s
AND parent NOT IN %(cost_center)s
GROUP BY parent
) as DCC_allocation
WHERE company=%(company)s
{additional_conditions}
AND posting_date <= %(to_date)s
AND is_cancelled = 0
AND cost_center = DCC_allocation.parent
""".format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", ''))
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
gl_entries = frappe.db.sql("""
select posting_date, account, debit, credit, is_opening, fiscal_year,
debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
and is_cancelled = 0
{distributed_cost_center_query}""".format(
additional_conditions=additional_conditions,
distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
and is_cancelled = 0""".format(
additional_conditions=additional_conditions), gl_filters, as_dict=True
)
if filters and filters.get('presentation_currency'):
convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company'))

View File

@ -176,44 +176,7 @@ def get_gl_entries(filters, accounting_dimensions):
if accounting_dimensions:
dimension_fields = ', '.join(accounting_dimensions) + ','
distributed_cost_center_query = ""
if filters and filters.get('cost_center'):
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit,
credit*(DCC_allocation.percentage_allocation/100) as credit,
debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
distributed_cost_center_query = """
UNION ALL
SELECT name as gl_entry,
posting_date,
account,
party_type,
party,
voucher_type,
voucher_no, {dimension_fields}
cost_center, project,
against_voucher_type,
against_voucher,
account_currency,
remarks, against,
is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
FROM `tabGL Entry`,
(
SELECT parent, sum(percentage_allocation) as percentage_allocation
FROM `tabDistributed Cost Center`
WHERE cost_center IN %(cost_center)s
AND parent NOT IN %(cost_center)s
GROUP BY parent
) as DCC_allocation
WHERE company=%(company)s
{conditions}
AND posting_date <= %(to_date)s
AND cost_center = DCC_allocation.parent
""".format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
gl_entries = frappe.db.sql(
"""
gl_entries = frappe.db.sql("""
select
name as gl_entry, posting_date, account, party_type, party,
voucher_type, voucher_no, {dimension_fields}
@ -222,13 +185,11 @@ def get_gl_entries(filters, accounting_dimensions):
remarks, against, is_opening, creation {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
{distributed_cost_center_query}
{order_by_statement}
""".format(
dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
order_by_statement=order_by_statement
),
filters, as_dict=1)
""".format(
dimension_fields=dimension_fields, select_fields=select_fields,
conditions=get_conditions(filters), order_by_statement=order_by_statement
), filters, as_dict=1)
if filters.get('presentation_currency'):
return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company'))

View File

@ -109,7 +109,6 @@ def accumulate_values_into_parents(accounts, accounts_by_name):
def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
data = []
new_accounts = accounts
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
for d in accounts:
@ -123,19 +122,6 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
"currency": company_currency,
"based_on": based_on
}
if based_on == 'cost_center':
cost_center_doc = frappe.get_doc("Cost Center",d.name)
if not cost_center_doc.enable_distributed_cost_center:
DCC_allocation = frappe.db.sql("""SELECT parent, sum(percentage_allocation) as percentage_allocation
FROM `tabDistributed Cost Center`
WHERE cost_center IN %(cost_center)s
AND parent NOT IN %(cost_center)s
GROUP BY parent""",{'cost_center': [d.name]})
if DCC_allocation:
for account in new_accounts:
if account['name'] == DCC_allocation[0][0]:
for value in value_fields:
d[value] += account[value]*(DCC_allocation[0][1]/100)
for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3)

View File

@ -5,7 +5,7 @@
"label": "Profit and Loss"
}
],
"content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Accounts\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Profit and Loss\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chart of Accounts\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Journal Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payment Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Trial Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounting Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Payable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Financial Statements\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Multi Currency\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bank Statement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Subscription Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goods and Services Tax (GST India)\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Share Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Cost Center and Budgeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening and Closing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Profitability\", \"col\": 4}}]",
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Goods and Services Tax (GST India)\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}}]",
"creation": "2020-03-02 15:41:59.515192",
"docstatus": 0,
"doctype": "Workspace",
@ -230,6 +230,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
@ -346,6 +347,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
@ -527,16 +529,17 @@
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "KSA VAT Report",
"link_to": "KSA VAT",
"link_type": "Report",
"onboard": 0,
"only_for": "Saudi Arabia",
"type": "Link"
},
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "KSA VAT Report",
"link_count": 0,
"link_to": "KSA VAT",
"link_type": "Report",
"onboard": 0,
"only_for": "Saudi Arabia",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -1020,6 +1023,17 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Cost Center",
"hidden": 0,
"is_query_report": 0,
"label": "Cost Center Allocation",
"link_count": 0,
"link_to": "Cost Center Allocation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Cost Center",
"hidden": 0,
@ -1158,15 +1172,16 @@
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "KSA VAT Setting",
"link_to": "KSA VAT Setting",
"link_type": "DocType",
"onboard": 0,
"only_for": "Saudi Arabia",
"type": "Link"
},
"hidden": 0,
"is_query_report": 0,
"label": "KSA VAT Setting",
"link_count": 0,
"link_to": "KSA VAT Setting",
"link_type": "DocType",
"onboard": 0,
"only_for": "Saudi Arabia",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -1220,7 +1235,7 @@
"type": "Link"
}
],
"modified": "2021-08-27 12:15:52.872471",
"modified": "2022-01-13 17:25:09.835345",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@ -1229,7 +1244,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
"sequence_id": 2,
"sequence_id": 2.0,
"shortcuts": [
{
"label": "Chart of Accounts",
@ -1278,4 +1293,4 @@
}
],
"title": "Accounting"
}
}

View File

@ -108,6 +108,10 @@ frappe.ui.form.on('Asset', {
frm.trigger("create_asset_repair");
}, __("Manage"));
frm.add_custom_button(__("Split Asset"), function() {
frm.trigger("split_asset");
}, __("Manage"));
if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Adjust Asset Value"), function() {
frm.trigger("create_asset_value_adjustment");
@ -322,6 +326,43 @@ frappe.ui.form.on('Asset', {
});
},
split_asset: function(frm) {
const title = __('Split Asset');
const fields = [
{
fieldname: 'split_qty',
fieldtype: 'Int',
label: __('Split Qty'),
reqd: 1
}
];
let dialog = new frappe.ui.Dialog({
title: title,
fields: fields
});
dialog.set_primary_action(__('Split'), function() {
const dialog_data = dialog.get_values();
frappe.call({
args: {
"asset_name": frm.doc.name,
"split_qty": cint(dialog_data.split_qty)
},
method: "erpnext.assets.doctype.asset.asset.split_asset",
callback: function(r) {
let doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
});
dialog.hide();
});
dialog.show();
},
create_asset_value_adjustment: function(frm) {
frappe.call({
args: {

View File

@ -3,7 +3,7 @@
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
"creation": "2016-03-01 17:01:27.920130",
"creation": "2022-01-18 02:26:55.975005",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
@ -23,6 +23,7 @@
"asset_name",
"asset_category",
"location",
"split_from",
"custodian",
"department",
"disposal_date",
@ -35,6 +36,7 @@
"available_for_use_date",
"column_break_23",
"gross_purchase_amount",
"asset_quantity",
"purchase_date",
"section_break_23",
"calculate_depreciation",
@ -141,6 +143,7 @@
},
{
"allow_on_submit": 1,
"fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
@ -480,6 +483,19 @@
"fieldname": "section_break_36",
"fieldtype": "Section Break",
"label": "Finance Books"
},
{
"fieldname": "split_from",
"fieldtype": "Link",
"label": "Split From",
"options": "Asset",
"read_only": 1
},
{
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset"
}
],
"idx": 72,
@ -502,10 +518,11 @@
"link_fieldname": "asset"
}
],
"modified": "2021-06-24 14:58:51.097908",
"modified": "2022-01-30 20:19:24.680027",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@ -542,6 +559,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "asset_name",
"track_changes": 1
}

View File

@ -36,8 +36,10 @@ class Asset(AccountsController):
self.validate_asset_values()
self.validate_asset_and_reference()
self.validate_item()
self.validate_cost_center()
self.set_missing_values()
self.prepare_depreciation_data()
if not self.split_from:
self.prepare_depreciation_data()
self.validate_gross_and_purchase_amount()
if self.get("schedules"):
self.validate_expected_value_after_useful_life()
@ -95,6 +97,19 @@ class Asset(AccountsController):
elif item.is_stock_item:
frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code))
def validate_cost_center(self):
if not self.cost_center: return
cost_center_company = frappe.db.get_value('Cost Center', self.cost_center, 'company')
if cost_center_company != self.company:
frappe.throw(
_("Selected Cost Center {} doesn't belongs to {}").format(
frappe.bold(self.cost_center),
frappe.bold(self.company)
),
title=_("Invalid Cost Center")
)
def validate_in_use_date(self):
if not self.available_for_use_date:
frappe.throw(_("Available for use date is required"))
@ -188,142 +203,143 @@ class Asset(AccountsController):
start = self.clear_depreciation_schedule()
for finance_book in self.get('finance_books'):
self.validate_asset_finance_books(finance_book)
self._make_depreciation_schedule(finance_book, start, date_of_sale)
# value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation:
value_after_depreciation = flt(finance_book.value_after_depreciation)
else:
value_after_depreciation = (flt(self.gross_purchase_amount) -
flt(self.opening_accumulated_depreciation))
def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
self.validate_asset_finance_books(finance_book)
finance_book.value_after_depreciation = value_after_depreciation
value_after_depreciation = self._get_value_after_depreciation(finance_book)
finance_book.value_after_depreciation = value_after_depreciation
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked)
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked)
has_pro_rata = self.check_is_pro_rata(finance_book)
has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata:
number_of_pending_depreciations += 1
if has_pro_rata:
number_of_pending_depreciations += 1
skip_row = False
skip_row = False
for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row: continue
for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row: continue
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(finance_book.depreciation_start_date,
n * cint(finance_book.frequency_of_depreciation))
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(finance_book.depreciation_start_date,
n * cint(finance_book.frequency_of_depreciation))
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_sale:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
from_date, date_of_sale)
if depreciation_amount > 0:
self.append("schedules", {
"schedule_date": date_of_sale,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx
})
break
# For first row
if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
self.available_for_use_date, finance_book.depreciation_start_date)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not self.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
self.to_date = add_months(self.available_for_use_date,
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
depreciation_amount, schedule_date, self.to_date)
depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
depreciation_amount, finance_book.finance_book)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount: continue
value_after_depreciation -= flt(depreciation_amount,
self.precision("gross_purchase_amount"))
# Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life)
or value_after_depreciation < finance_book.expected_value_after_useful_life):
depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
skip_row = True
# if asset is being sold
if date_of_sale:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
from_date, date_of_sale)
if depreciation_amount > 0:
# With monthly depreciation, each depreciation is divided by months remaining until next date
if self.allow_monthly_depreciation:
# month range is 1 to 12
# In pro rata case, for first and last depreciation, month range would be different
month_range = months \
if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
else finance_book.frequency_of_depreciation
self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method,
finance_book.finance_book, finance_book.idx)
for r in range(month_range):
if (has_pro_rata and n == 0):
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
per_day_amt = depreciation_amount / days
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
depreciation_amount -= depreciation_amount_for_current_month
date = monthly_schedule_date
amount = depreciation_amount_for_current_month
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / (month_range - 1)
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
# For last entry of monthly depr
date = last_schedule_date
amount = depreciation_amount / month_range
break
# For first row
if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
from_date = add_days(self.available_for_use_date, -1) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
from_date, finance_book.depreciation_start_date)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not self.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
self.to_date = add_months(self.available_for_use_date,
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
depreciation_amount, schedule_date, self.to_date)
depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
depreciation_amount, finance_book.finance_book)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount: continue
value_after_depreciation -= flt(depreciation_amount,
self.precision("gross_purchase_amount"))
# Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life)
or value_after_depreciation < finance_book.expected_value_after_useful_life):
depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
skip_row = True
if depreciation_amount > 0:
# With monthly depreciation, each depreciation is divided by months remaining until next date
if self.allow_monthly_depreciation:
# month range is 1 to 12
# In pro rata case, for first and last depreciation, month range would be different
month_range = months \
if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
else finance_book.frequency_of_depreciation
for r in range(month_range):
if (has_pro_rata and n == 0):
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
per_day_amt = depreciation_amount / days
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
depreciation_amount -= depreciation_amount_for_current_month
date = monthly_schedule_date
amount = depreciation_amount_for_current_month
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
amount = depreciation_amount / (month_range - 1)
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
# For last entry of monthly depr
date = last_schedule_date
amount = depreciation_amount / month_range
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
self.append("schedules", {
"schedule_date": date,
"depreciation_amount": amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx
})
else:
self.append("schedules", {
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx
})
self._add_depreciation_row(date, amount, finance_book.depreciation_method,
finance_book.finance_book, finance_book.idx)
else:
self._add_depreciation_row(schedule_date, depreciation_amount, finance_book.depreciation_method,
finance_book.finance_book, finance_book.idx)
def _add_depreciation_row(self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id):
self.append("schedules", {
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": depreciation_method,
"finance_book": finance_book,
"finance_book_id": finance_book_id
})
def _get_value_after_depreciation(self, finance_book):
# value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation:
value_after_depreciation = flt(finance_book.value_after_depreciation)
else:
value_after_depreciation = (flt(self.gross_purchase_amount) -
flt(self.opening_accumulated_depreciation))
return value_after_depreciation
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book
@ -333,7 +349,6 @@ class Asset(AccountsController):
depr_schedule = []
for schedule in self.get('schedules'):
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
if len(start) == (int(schedule.finance_book_id) - 2):
start.append(num_of_depreciations_completed)
@ -374,7 +389,9 @@ class Asset(AccountsController):
if from_date:
return from_date
return self.available_for_use_date
# since depr for available_for_use_date is not yet booked
return add_days(self.available_for_use_date, -1)
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
@ -907,3 +924,113 @@ def get_depreciation_amount(asset, depreciable_value, row):
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount
@frappe.whitelist()
def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name)
split_qty = cint(split_qty)
if split_qty >= asset.asset_quantity:
frappe.throw(_("Split qty cannot be grater than or equal to asset qty"))
remaining_qty = asset.asset_quantity - split_qty
new_asset = create_new_asset_after_split(asset, split_qty)
update_existing_asset(asset, remaining_qty)
return new_asset
def update_existing_asset(asset, remaining_qty):
remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity)
opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity)
frappe.db.set_value("Asset", asset.name, {
'opening_accumulated_depreciation': opening_accumulated_depreciation,
'gross_purchase_amount': remaining_gross_purchase_amount,
'asset_quantity': remaining_qty
})
for finance_book in asset.get('finance_books'):
value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.asset_quantity)
expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * remaining_qty)/asset.asset_quantity)
frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation)
frappe.db.set_value('Asset Finance Book', finance_book.name, 'expected_value_after_useful_life', expected_value_after_useful_life)
accumulated_depreciation = 0
for term in asset.get('schedules'):
depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.asset_quantity)
frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount)
accumulated_depreciation += depreciation_amount
frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', accumulated_depreciation)
def create_new_asset_after_split(asset, split_qty):
new_asset = frappe.copy_doc(asset)
new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity)
opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity)
new_asset.gross_purchase_amount = new_gross_purchase_amount
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name
accumulated_depreciation = 0
for finance_book in new_asset.get('finance_books'):
finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.asset_quantity)
finance_book.expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * split_qty)/asset.asset_quantity)
for term in new_asset.get('schedules'):
depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.asset_quantity)
term.depreciation_amount = depreciation_amount
accumulated_depreciation += depreciation_amount
term.accumulated_depreciation_amount = accumulated_depreciation
new_asset.submit()
new_asset.set_status()
for term in new_asset.get('schedules'):
# Update references in JV
if term.journal_entry:
add_reference_in_jv_on_split(term.journal_entry, new_asset.name, asset.name, term.depreciation_amount)
return new_asset
def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount):
journal_entry = frappe.get_doc('Journal Entry', entry_name)
entries_to_add = []
idx = len(journal_entry.get('accounts')) + 1
for account in journal_entry.get('accounts'):
if account.reference_name == old_asset_name:
entries_to_add.append(frappe.copy_doc(account).as_dict())
if account.credit:
account.credit = account.credit - depreciation_amount
account.credit_in_account_currency = account.credit_in_account_currency - \
account.exchange_rate * depreciation_amount
elif account.debit:
account.debit = account.debit - depreciation_amount
account.debit_in_account_currency = account.debit_in_account_currency - \
account.exchange_rate * depreciation_amount
for entry in entries_to_add:
entry.reference_name = new_asset_name
if entry.credit:
entry.credit = depreciation_amount
entry.credit_in_account_currency = entry.exchange_rate * depreciation_amount
elif entry.debit:
entry.debit = depreciation_amount
entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount
entry.idx = idx
idx += 1
journal_entry.append('accounts', entry)
journal_entry.flags.ignore_validate_update_after_submit = True
journal_entry.save()
# Repost GL Entries
journal_entry.docstatus = 2
journal_entry.make_gl_entries(1)
journal_entry.docstatus = 1
journal_entry.make_gl_entries()

View File

@ -7,7 +7,7 @@ import frappe
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
from erpnext.assets.doctype.asset.depreciation import (
post_depreciation_entries,
restore_asset,
@ -134,6 +134,29 @@ class TestAsset(AssetSetup):
pr.cancel()
self.assertEqual(asset.docstatus, 2)
def test_purchase_of_grouped_asset(self):
create_fixed_asset_item("Rack", is_grouped_asset=1)
pr = make_purchase_receipt(item_code="Rack", qty=3, rate=100000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
self.assertEqual(asset.asset_quantity, 3)
asset.calculate_depreciation = 1
month_end_date = get_last_day(nowdate())
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
asset.available_for_use_date = purchase_date
asset.purchase_date = purchase_date
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": month_end_date
})
asset.submit()
def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset = 1)
doc = frappe.new_doc('Purchase Invoice')
@ -207,9 +230,9 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_gle = (
("_Test Accumulated Depreciations - _TC", 20392.16, 0.0),
("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0),
("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
("Debtors - _TC", 25000.0, 0.0)
)
@ -222,6 +245,57 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_splitting(self):
asset = create_asset(
calculate_depreciation = 1,
asset_quantity=10,
available_for_use_date = '2020-01-01',
purchase_date = '2020-01-01',
expected_value_after_useful_life = 0,
total_number_of_depreciations = 6,
number_of_depreciations_booked = 1,
frequency_of_depreciation = 10,
depreciation_start_date = '2021-01-01',
opening_accumulated_depreciation=20000,
gross_purchase_amount=120000,
submit = 1
)
post_depreciation_entries(date="2021-01-01")
self.assertEqual(asset.asset_quantity, 10)
self.assertEqual(asset.gross_purchase_amount, 120000)
self.assertEqual(asset.opening_accumulated_depreciation, 20000)
new_asset = split_asset(asset.name, 2)
asset.load_from_db()
self.assertEqual(new_asset.asset_quantity, 2)
self.assertEqual(new_asset.gross_purchase_amount, 24000)
self.assertEqual(new_asset.opening_accumulated_depreciation, 4000)
self.assertEqual(new_asset.split_from, asset.name)
self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000)
self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000)
self.assertEqual(asset.asset_quantity, 8)
self.assertEqual(asset.gross_purchase_amount, 96000)
self.assertEqual(asset.opening_accumulated_depreciation, 16000)
self.assertEqual(asset.schedules[0].depreciation_amount, 16000)
self.assertEqual(asset.schedules[1].depreciation_amount, 16000)
journal_entry = asset.schedules[0].journal_entry
jv = frappe.get_doc('Journal Entry', journal_entry)
self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000)
self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000)
self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000)
self.assertEqual(jv.accounts[3].debit_in_account_currency, 4000)
self.assertEqual(jv.accounts[0].reference_name, asset.name)
self.assertEqual(jv.accounts[1].reference_name, asset.name)
self.assertEqual(jv.accounts[2].reference_name, new_asset.name)
self.assertEqual(jv.accounts[3].reference_name, new_asset.name)
def test_expense_head(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=2, rate=200000.0, location="Test Location")
@ -491,10 +565,10 @@ class TestDepreciationMethods(AssetSetup):
)
expected_schedules = [
["2030-12-31", 27534.25, 27534.25],
["2031-12-31", 30000.0, 57534.25],
["2032-12-31", 30000.0, 87534.25],
["2033-01-30", 2465.75, 90000.0]
['2030-12-31', 27616.44, 27616.44],
['2031-12-31', 30000.0, 57616.44],
['2032-12-31', 30000.0, 87616.44],
['2033-01-30', 2383.56, 90000.0]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@ -544,10 +618,10 @@ class TestDepreciationMethods(AssetSetup):
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
["2030-12-31", 28493.15, 28493.15],
["2031-12-31", 35753.43, 64246.58],
["2032-12-31", 17876.71, 82123.29],
["2033-06-06", 5376.71, 87500.0]
['2030-12-31', 28630.14, 28630.14],
['2031-12-31', 35684.93, 64315.07],
['2032-12-31', 17842.47, 82157.54],
['2033-06-06', 5342.46, 87500.0]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@ -580,10 +654,10 @@ class TestDepreciationMethods(AssetSetup):
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
["2030-12-31", 11780.82, 11780.82],
["2031-12-31", 44109.59, 55890.41],
["2032-12-31", 22054.8, 77945.21],
["2033-07-12", 9554.79, 87500.0]
["2030-12-31", 11849.32, 11849.32],
["2031-12-31", 44075.34, 55924.66],
["2032-12-31", 22037.67, 77962.33],
["2033-07-12", 9537.67, 87500.0]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@ -621,7 +695,7 @@ class TestDepreciationBasics(AssetSetup):
asset = create_asset(
item_code = "Macbook Pro",
calculate_depreciation = 1,
available_for_use_date = getdate("2019-12-31"),
available_for_use_date = getdate("2020-01-01"),
total_number_of_depreciations = 3,
expected_value_after_useful_life = 10000,
depreciation_start_date = getdate("2020-07-01"),
@ -632,7 +706,7 @@ class TestDepreciationBasics(AssetSetup):
["2020-07-01", 15000, 15000],
["2021-07-01", 30000, 45000],
["2022-07-01", 30000, 75000],
["2022-12-31", 15000, 90000]
["2023-01-01", 15000, 90000]
]
for i, schedule in enumerate(asset.schedules):
@ -1109,6 +1183,7 @@ class TestDepreciationBasics(AssetSetup):
self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0)
def test_expected_value_change(self):
"""
tests if changing `expected_value_after_useful_life`
@ -1130,6 +1205,15 @@ class TestDepreciationBasics(AssetSetup):
asset.reload()
self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0)
def test_asset_cost_center(self):
asset = create_asset(is_existing_asset = 1, do_not_save=1)
asset.cost_center = "Main - WP"
self.assertRaises(frappe.ValidationError, asset.submit)
asset.cost_center = "Main - _TC"
asset.submit()
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
@ -1164,7 +1248,8 @@ def create_asset(**args):
"available_for_use_date": args.available_for_use_date or "2020-06-06",
"location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1
"is_existing_asset": args.is_existing_asset or 1,
"asset_quantity": args.get("asset_quantity") or 1
})
if asset.calculate_depreciation:
@ -1202,13 +1287,13 @@ def create_asset_category():
})
asset_category.insert()
def create_fixed_asset_item():
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0):
meta = frappe.get_meta('Asset')
naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-'
try:
frappe.get_doc({
item = frappe.get_doc({
"doctype": "Item",
"item_code": "Macbook Pro",
"item_code": item_code or "Macbook Pro",
"item_name": "Macbook Pro",
"description": "Macbook Pro Retina Display",
"asset_category": "Computers",
@ -1216,11 +1301,14 @@ def create_fixed_asset_item():
"stock_uom": "Nos",
"is_stock_item": 0,
"is_fixed_asset": 1,
"auto_create_assets": 1,
"auto_create_assets": auto_create_assets,
"is_grouped_asset": is_grouped_asset,
"asset_naming_series": naming_series
}).insert()
})
item.insert()
except frappe.DuplicateEntryError:
pass
return item
def set_depreciation_settings_in_company():
company = frappe.get_doc("Company", "_Test Company")

View File

@ -5,7 +5,7 @@
"label": "Asset Value Analytics"
}
],
"content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Assets\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Asset Value Analytics\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset Category\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fixed Asset Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assets\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Assets\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset Category\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fixed Asset Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:27.634865",
"docstatus": 0,
"doctype": "Workspace",
@ -172,7 +172,7 @@
"type": "Link"
}
],
"modified": "2021-08-05 12:15:54.839453",
"modified": "2022-01-13 17:25:41.730628",
"modified_by": "Administrator",
"module": "Assets",
"name": "Assets",
@ -181,7 +181,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
"sequence_id": 4,
"sequence_id": 4.0,
"shortcuts": [
{
"label": "Asset",

View File

@ -6,14 +6,17 @@
"document_type": "Other",
"engine": "InnoDB",
"field_order": [
"supplier_and_price_defaults_section",
"supp_master_name",
"supplier_group",
"column_break_4",
"buying_price_list",
"maintain_same_rate_action",
"role_to_override_stop_action",
"column_break_3",
"transaction_settings_section",
"po_required",
"pr_required",
"column_break_12",
"maintain_same_rate",
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
@ -42,10 +45,6 @@
"label": "Default Buying Price List",
"options": "Price List"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "po_required",
"fieldtype": "Select",
@ -73,7 +72,7 @@
{
"fieldname": "subcontract",
"fieldtype": "Section Break",
"label": "Subcontract"
"label": "Subcontracting Settings"
},
{
"default": "Material Transferred for Subcontract",
@ -116,6 +115,24 @@
"fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
"fieldtype": "Check",
"label": "Bill for Rejected Quantity in Purchase Invoice"
},
{
"fieldname": "supplier_and_price_defaults_section",
"fieldtype": "Section Break",
"label": "Supplier and Price Defaults"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "transaction_settings_section",
"fieldtype": "Section Break",
"label": "Transaction Settings"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-cog",
@ -123,7 +140,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-09-08 19:26:23.548837",
"modified": "2022-01-27 17:57:58.367048",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@ -141,5 +158,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@ -131,28 +131,6 @@ class Supplier(TransactionBase):
if frappe.defaults.get_global_default('supp_master_name') == 'Supplier Name':
frappe.db.set(self, "supplier_name", newdn)
def create_onboarding_docs(self, args):
company = frappe.defaults.get_defaults().get('company') or \
frappe.db.get_single_value('Global Defaults', 'default_company')
for i in range(1, args.get('max_count')):
supplier = args.get('supplier_name_' + str(i))
if supplier:
try:
doc = frappe.get_doc({
'doctype': self.doctype,
'supplier_name': supplier,
'supplier_group': _('Local'),
'company': company
}).insert()
if args.get('supplier_email_' + str(i)):
from erpnext.selling.doctype.customer.customer import create_contact
create_contact(supplier, 'Supplier',
doc.name, args.get('supplier_email_' + str(i)))
except frappe.NameError:
pass
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):

View File

@ -1,49 +0,0 @@
{
"add_more_button": 1,
"app": "ERPNext",
"creation": "2019-11-15 14:45:32.626641",
"docstatus": 0,
"doctype": "Onboarding Slide",
"domains": [],
"help_links": [
{
"label": "Learn More",
"video_id": "zsrrVDk6VBs"
}
],
"idx": 0,
"image_src": "",
"is_completed": 0,
"max_count": 3,
"modified": "2019-12-09 17:54:18.452038",
"modified_by": "Administrator",
"name": "Add A Few Suppliers",
"owner": "Administrator",
"ref_doctype": "Supplier",
"slide_desc": "",
"slide_fields": [
{
"align": "",
"fieldname": "supplier_name",
"fieldtype": "Data",
"label": "Supplier Name",
"placeholder": "",
"reqd": 1
},
{
"align": "",
"fieldtype": "Column Break",
"reqd": 0
},
{
"align": "",
"fieldname": "supplier_email",
"fieldtype": "Data",
"label": "Supplier Email",
"reqd": 1
}
],
"slide_order": 50,
"slide_title": "Add A Few Suppliers",
"slide_type": "Create"
}

View File

@ -5,7 +5,7 @@
"label": "Purchase Order Trends"
}
],
"content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Buying\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Purchase Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Buying\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items & Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier Scorecard\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Regional\", \"col\": 4}}]",
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Buying\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]",
"creation": "2020-01-28 11:50:26.195467",
"docstatus": 0,
"doctype": "Workspace",
@ -509,7 +509,7 @@
"type": "Link"
}
],
"modified": "2021-08-05 12:15:56.218428",
"modified": "2022-01-13 17:26:39.090190",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
@ -518,7 +518,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
"sequence_id": 6,
"sequence_id": 6.0,
"shortcuts": [
{
"color": "Green",

View File

@ -1,49 +1,10 @@
# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# GPL v3 License. See license.txt
import click
import frappe
from frappe.commands import get_site, pass_context
def call_command(cmd, context):
return click.Context(cmd, obj=context).forward(cmd)
@click.command('make-demo')
@click.option('--site', help='site name')
@click.option('--domain', default='Manufacturing')
@click.option('--days', default=100,
help='Run the demo for so many days. Default 100')
@click.option('--resume', default=False, is_flag=True,
help='Continue running the demo for given days')
@click.option('--reinstall', default=False, is_flag=True,
help='Reinstall site before demo')
@pass_context
def make_demo(context, site, domain='Manufacturing', days=100,
resume=False, reinstall=False):
"Reinstall site and setup demo"
from frappe.commands.site import _reinstall
from frappe.installer import install_app
site = get_site(context)
if resume:
with frappe.init_site(site):
frappe.connect()
from erpnext.demo import demo
demo.simulate(days=days)
else:
if reinstall:
_reinstall(site, yes=True)
with frappe.init_site(site=site):
frappe.connect()
if not 'erpnext' in frappe.get_installed_apps():
install_app('erpnext')
# import needs site
from erpnext.demo import demo
demo.make(domain, days)
commands = [
make_demo
]
commands = []

View File

@ -167,9 +167,14 @@ class AccountsController(TransactionBase):
validate_regional(self)
validate_einvoice_fields(self)
if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self)
def before_cancel(self):
validate_einvoice_fields(self)
def on_trash(self):
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
@ -185,8 +190,6 @@ class AccountsController(TransactionBase):
frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
elif getdate(self.posting_date) > getdate(d.service_end_date):
frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
elif getdate(self.posting_date) > getdate(d.service_start_date):
frappe.throw(_("Row #{0}: Service Start Date cannot be before Invoice Posting Date").format(d.idx))
def validate_invoice_documents_schedule(self):
self.validate_payment_schedule_dates()
@ -2153,3 +2156,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
@erpnext.allow_regional
def validate_regional(doc):
pass
@erpnext.allow_regional
def validate_einvoice_fields(doc):
pass

View File

@ -70,9 +70,18 @@ class BuyingController(StockController, Subcontracting):
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'),
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template')))
self.update_if_missing(
get_party_details(
self.supplier,
party_type="Supplier",
doctype=self.doctype,
company=self.company,
party_address=self.get("supplier_address"),
shipping_address=self.get('shipping_address'),
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'),
ignore_permissions=self.flags.ignore_permissions
)
)
self.set_missing_item_details(for_validate)
@ -554,10 +563,13 @@ class BuyingController(StockController, Subcontracting):
# Check for asset naming series
if item_data.get('asset_naming_series'):
created_assets = []
for qty in range(cint(d.qty)):
asset = self.make_asset(d)
if item_data.get('is_grouped_asset'):
asset = self.make_asset(d, is_grouped_asset=True)
created_assets.append(asset)
else:
for qty in range(cint(d.qty)):
asset = self.make_asset(d)
created_assets.append(asset)
if len(created_assets) > 5:
# dont show asset form links if more than 5 assets are created
@ -580,14 +592,18 @@ class BuyingController(StockController, Subcontracting):
for message in messages:
frappe.msgprint(message, title="Success", indicator="green")
def make_asset(self, row):
def make_asset(self, row, is_grouped_asset=False):
if not row.asset_location:
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
item_data = frappe.db.get_value('Item',
row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1)
purchase_amount = flt(row.base_rate + row.item_tax_amount)
if is_grouped_asset:
purchase_amount = flt(row.base_amount + row.item_tax_amount)
else:
purchase_amount = flt(row.base_rate + row.item_tax_amount)
asset = frappe.get_doc({
'doctype': 'Asset',
'item_code': row.item_code,
@ -601,6 +617,7 @@ class BuyingController(StockController, Subcontracting):
'calculate_depreciation': 1,
'purchase_receipt_amount': purchase_amount,
'gross_purchase_amount': purchase_amount,
'asset_quantity': row.qty if is_grouped_asset else 0,
'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None,
'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None
})
@ -687,7 +704,7 @@ class BuyingController(StockController, Subcontracting):
def get_asset_item_details(asset_items):
asset_items_data = {}
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series", "is_grouped_asset"],
filters = {'name': ('in', asset_items)}):
asset_items_data.setdefault(d.name, d)

View File

@ -132,13 +132,17 @@ class EmployeeBoardingController(Document):
def on_cancel(self):
# delete task project
for task in frappe.get_all('Task', filters={'project': self.project}):
project = self.project
for task in frappe.get_all('Task', filters={'project': project}):
frappe.delete_doc('Task', task.name, force=1)
frappe.delete_doc('Project', self.project, force=1)
frappe.delete_doc('Project', project, force=1)
self.db_set('project', '')
for activity in self.activities:
activity.db_set('task', '')
frappe.msgprint(_('Linked Project {} and Tasks deleted.').format(
project), alert=True, indicator='blue')
@frappe.whitelist()
def get_onboarding_details(parent, parenttype):

View File

@ -132,7 +132,7 @@ def find_variant(template, args, variant_item_code=None):
conditions = " or ".join(conditions)
from erpnext.portal.product_configurator.utils import get_item_codes_by_attributes
from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes
possible_variants = [i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code]
for variant in possible_variants:
@ -262,9 +262,8 @@ def generate_keyed_value_combinations(args):
def copy_attributes_to_variant(item, variant):
# copy non no-copy fields
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate",
"has_variants", "attributes"]
exclude_fields = ["naming_series", "item_code", "item_name", "published_in_website",
"opening_stock", "variant_of", "valuation_rate"]
if item.variant_based_on=='Manufacturer':
# don't copy manufacturer values if based on part no

View File

@ -249,6 +249,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
del filters['customer']
else:
del filters['supplier']
else:
filters.pop('customer', None)
filters.pop('supplier', None)
description_cond = ''
@ -707,6 +710,7 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
item_group = filters.get('item_group')
company = filters.get('company')
taxes = item_doc.taxes or []
while item_group:
@ -715,7 +719,7 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
item_group = item_group_doc.parent_item_group
if not taxes:
return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """)
return frappe.get_all('Item Tax Template', filters={'disabled': 0, 'company': company}, as_list=True)
else:
valid_from = filters.get('valid_from')
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
@ -724,7 +728,7 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
'item_code': filters.get('item_code'),
'posting_date': valid_from,
'tax_category': filters.get('tax_category'),
'company': filters.get('company')
'company': company
}
taxes = _get_item_tax_template(args, taxes, for_validate=True)

View File

@ -74,7 +74,8 @@ class SellingController(StockController):
doctype=self.doctype, company=self.company,
posting_date=self.get('posting_date'),
fetch_payment_terms_template=fetch_payment_terms_template,
party_address=self.customer_address, shipping_address=self.shipping_address_name)
party_address=self.customer_address, shipping_address=self.shipping_address_name,
company_address=self.get('company_address'))
if not self.meta.get_field("sales_team"):
party_details.pop("sales_team")
self.update_if_missing(party_details)
@ -204,7 +205,7 @@ class SellingController(StockController):
valuation_rate_map = {}
for item in self.items:
if not item.item_code:
if not item.item_code or item.is_free_item:
continue
last_purchase_rate, is_stock_item = frappe.get_cached_value(
@ -251,7 +252,7 @@ class SellingController(StockController):
valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
for item in self.items:
if not item.item_code:
if not item.item_code or item.is_free_item:
continue
last_valuation_rate = valuation_rate_map.get(
@ -385,7 +386,7 @@ class SellingController(StockController):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
if not d.incoming_rate:
if not (self.get("is_return") and d.incoming_rate):
d.incoming_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,

View File

@ -400,6 +400,16 @@ class StatusUpdater(Document):
ref_doc = frappe.get_doc(ref_dt, ref_dn)
ref_doc.db_set("per_billed", per_billed)
# set billling status
if hasattr(ref_doc, 'billing_status'):
if ref_doc.per_billed < 0.001:
ref_doc.db_set("billing_status", "Not Billed")
elif ref_doc.per_billed > 99.999999:
ref_doc.db_set("billing_status", "Fully Billed")
else:
ref_doc.db_set("billing_status", "Partly Billed")
ref_doc.set_status(update=True)
def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):

View File

@ -17,7 +17,7 @@ from erpnext.accounts.general_ledger import (
from erpnext.accounts.utils import get_fiscal_year
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.stock_ledger import get_items_to_be_repost, get_valuation_rate
from erpnext.stock.stock_ledger import get_items_to_be_repost
class QualityInspectionRequiredError(frappe.ValidationError): pass
@ -40,7 +40,10 @@ class StockController(AccountsController):
if self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
provisional_accounting_for_non_stock_items = \
cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
if cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items:
warehouse_account = get_warehouse_account_map(self.company)
if self.docstatus==1:
@ -77,17 +80,17 @@ class StockController(AccountsController):
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
def clean_serial_nos(self):
from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string
for row in self.get("items"):
if hasattr(row, "serial_no") and row.serial_no:
# replace commas by linefeed
row.serial_no = row.serial_no.replace(",", "\n")
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
# strip preceeding and succeeding spaces for each SN
# (SN could have valid spaces in between e.g. SN - 123 - 2021)
serial_no_list = row.serial_no.split("\n")
serial_no_list = [sn.strip() for sn in serial_no_list]
row.serial_no = "\n".join(serial_no_list)
for row in self.get('packed_items') or []:
if hasattr(row, "serial_no") and row.serial_no:
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None):
@ -111,17 +114,6 @@ class StockController(AccountsController):
self.check_expense_account(item_row)
# If the item does not have the allow zero valuation rate flag set
# and ( valuation rate not mentioned in an incoming entry
# or incoming entry not found while delivering the item),
# try to pick valuation rate from previous sle or Item master and update in SLE
# Otherwise, throw an exception
if not sle.stock_value_difference and self.doctype != "Stock Reconciliation" \
and not item_row.get("allow_zero_valuation_rate"):
sle = self.update_stock_ledger_entries(sle)
# expense account/ target_warehouse / source_warehouse
if item_row.get('target_warehouse'):
warehouse = item_row.get('target_warehouse')
@ -164,26 +156,6 @@ class StockController(AccountsController):
return frappe.flags.debit_field_precision
def update_stock_ledger_entries(self, sle):
sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
self.doctype, self.name, currency=self.company_currency, company=self.company)
sle.stock_value = flt(sle.qty_after_transaction) * flt(sle.valuation_rate)
sle.stock_value_difference = flt(sle.actual_qty) * flt(sle.valuation_rate)
if sle.name:
frappe.db.sql("""
update
`tabStock Ledger Entry`
set
stock_value = %(stock_value)s,
valuation_rate = %(valuation_rate)s,
stock_value_difference = %(stock_value_difference)s
where
name = %(name)s""", (sle))
return sle
def get_voucher_details(self, default_expense_account, default_cost_center, sle_map):
if self.doctype == "Stock Reconciliation":
reconciliation_purpose = frappe.db.get_value(self.doctype, self.name, "purpose")
@ -287,11 +259,7 @@ class StockController(AccountsController):
for d in self.items:
if not d.batch_no: continue
serial_nos = [sr.name for sr in frappe.get_all("Serial No",
{'batch_no': d.batch_no, 'status': 'Inactive'})]
if serial_nos:
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
frappe.db.set_value("Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None)
d.batch_no = None
d.db_set("batch_no", None)

View File

@ -56,6 +56,12 @@ class TestQueries(unittest.TestCase):
bundled_stock_items = query(txt="_test product bundle item 5", filters={"is_stock_item": 1})
self.assertEqual(len(bundled_stock_items), 0)
# empty customer/supplier should be stripped of instead of failure
query(txt="", filters={"customer": None})
query(txt="", filters={"customer": ""})
query(txt="", filters={"supplier": None})
query(txt="", filters={"supplier": ""})
def test_bom_qury(self):
query = add_default_params(queries.bom, "BOM")

View File

@ -5,7 +5,7 @@ frappe.ui.form.on('Campaign', {
refresh: function(frm) {
erpnext.toggle_naming_series();
if (frm.doc.__islocal) {
if (frm.is_new()) {
frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
} else {
cur_frm.add_custom_button(__("View Leads"), function() {

View File

@ -1,9 +1,10 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe.model.document import Document
class CRMSettings(Document):
pass
def validate(self):
frappe.db.set_default("campaign_naming_by", self.get("campaign_naming_by", ""))

View File

@ -2,13 +2,14 @@
# For license information, please see license.txt
from urllib.parse import urlencode
import frappe
import requests
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_url_to_form
from frappe.utils.file_manager import get_file_path
from six.moves.urllib.parse import urlencode
class LinkedInSettings(Document):

View File

@ -24,6 +24,14 @@ frappe.ui.form.on("Opportunity", {
frm.trigger('set_contact_link');
}
},
validate: function(frm) {
if (frm.doc.status == "Lost" && !frm.doc.lost_reasons.length) {
frm.trigger('set_as_lost_dialog');
frappe.throw(__("Lost Reasons are required in case opportunity is Lost."));
}
},
contact_date: function(frm) {
if(frm.doc.contact_date < frappe.datetime.now_datetime()){
frm.set_value("contact_date", "");
@ -82,7 +90,7 @@ frappe.ui.form.on("Opportunity", {
frm.trigger('setup_opportunity_from');
erpnext.toggle_naming_series();
if(!doc.__islocal && doc.status!=="Lost") {
if(!frm.is_new() && doc.status!=="Lost") {
if(doc.with_items){
frm.add_custom_button(__('Supplier Quotation'),
function() {
@ -187,11 +195,11 @@ frappe.ui.form.on("Opportunity", {
change_form_labels: function(frm) {
let company_currency = erpnext.get_currency(frm.doc.company);
frm.set_currency_labels(["base_opportunity_amount", "base_total", "base_grand_total"], company_currency);
frm.set_currency_labels(["opportunity_amount", "total", "grand_total"], frm.doc.currency);
frm.set_currency_labels(["base_opportunity_amount", "base_total"], company_currency);
frm.set_currency_labels(["opportunity_amount", "total"], frm.doc.currency);
// toggle fields
frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total", "base_grand_total"],
frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total"],
frm.doc.currency != company_currency);
},
@ -209,20 +217,15 @@ frappe.ui.form.on("Opportunity", {
},
calculate_total: function(frm) {
let total = 0, base_total = 0, grand_total = 0, base_grand_total = 0;
let total = 0, base_total = 0;
frm.doc.items.forEach(item => {
total += item.amount;
base_total += item.base_amount;
})
base_grand_total = base_total + frm.doc.base_opportunity_amount;
grand_total = total + frm.doc.opportunity_amount;
frm.set_value({
'total': flt(total),
'base_total': flt(base_total),
'grand_total': flt(grand_total),
'base_grand_total': flt(base_grand_total)
'base_total': flt(base_total)
});
}

View File

@ -42,10 +42,8 @@
"items",
"section_break_32",
"base_total",
"base_grand_total",
"column_break_33",
"total",
"grand_total",
"contact_info",
"customer_address",
"address_display",
@ -475,21 +473,6 @@
"fieldname": "column_break_33",
"fieldtype": "Column Break"
},
{
"fieldname": "base_grand_total",
"fieldtype": "Currency",
"label": "Grand Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "grand_total",
"fieldtype": "Currency",
"label": "Grand Total",
"options": "currency",
"read_only": 1
},
{
"fieldname": "lost_detail_section",
"fieldtype": "Section Break",
@ -510,7 +493,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
"modified": "2021-10-21 12:04:30.151379",
"modified": "2022-01-29 19:32:26.382896",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
@ -547,6 +530,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"subject_field": "title",
"timeline_field": "party_name",
"title_field": "title",

View File

@ -69,8 +69,6 @@ class Opportunity(TransactionBase):
self.total = flt(total)
self.base_total = flt(base_total)
self.grand_total = flt(self.total) + flt(self.opportunity_amount)
self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount)
def make_new_lead_if_required(self):
"""Set lead against new opportunity"""

View File

@ -3,6 +3,8 @@
frappe.ui.form.on('Prospect', {
refresh (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
frm.add_custom_button(__("Customer"), function() {
frappe.model.open_mapped_doc({

View File

@ -16,7 +16,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM",
"idx": 0,
"is_complete": 0,
"modified": "2020-07-08 14:05:42.644448",
"modified": "2022-01-29 20:14:29.502145",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
@ -33,6 +33,9 @@
},
{
"step": "Create and Send Quotation"
},
{
"step": "CRM Settings"
}
],
"subtitle": "Lead, Opportunity, Customer, and more.",

View File

@ -5,7 +5,6 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-28 21:07:11.461172",
@ -13,6 +12,7 @@
"name": "Create and Send Quotation",
"owner": "Administrator",
"reference_document": "Quotation",
"show_form_tour": 0,
"show_full_form": 1,
"title": "Create and Send Quotation",
"validate_action": 1

View File

@ -5,7 +5,6 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-28 21:07:01.373403",
@ -13,6 +12,7 @@
"name": "Create Lead",
"owner": "Administrator",
"reference_document": "Lead",
"show_form_tour": 0,
"show_full_form": 1,
"title": "Create Lead",
"validate_action": 1

View File

@ -5,7 +5,6 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-01-21 15:28:52.483839",
@ -13,6 +12,7 @@
"name": "Create Opportunity",
"owner": "Administrator",
"reference_document": "Opportunity",
"show_form_tour": 0,
"show_full_form": 1,
"title": "Create Opportunity",
"validate_action": 1

View File

@ -0,0 +1,21 @@
{
"action": "Go to Page",
"creation": "2022-01-29 20:14:24.803844",
"description": "# CRM Settings\n\nCRM module\u2019s features are configurable as per your business needs. CRM Settings is the place where you can set your preferences for:\n- Campaign\n- Lead\n- Opportunity\n- Quotation",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 1,
"is_skipped": 0,
"modified": "2022-01-29 20:14:24.803844",
"modified_by": "Administrator",
"name": "CRM Settings",
"owner": "Administrator",
"path": "#crm-settings/CRM%20Settings",
"reference_document": "CRM Settings",
"show_form_tour": 0,
"show_full_form": 0,
"title": "CRM Settings",
"validate_action": 1
}

View File

@ -5,13 +5,13 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:28:16.448676",
"modified_by": "Administrator",
"name": "Introduction to CRM",
"owner": "Administrator",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Introduction to CRM",
"validate_action": 1,

View File

@ -1,10 +1,11 @@
{
"charts": [
{
"chart_name": "Territory Wise Sales"
"chart_name": "Territory Wise Sales",
"label": "Territory Wise Sales"
}
],
"content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"CRM\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Lead\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Opportunity\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Sales Pipeline\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Campaign\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]",
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"CRM\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Territory Wise Sales\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Opportunity\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Sales Pipeline\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Campaign\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-01-23 14:48:30.183272",
"docstatus": 0,
"doctype": "Workspace",
@ -144,6 +145,7 @@
"hidden": 0,
"is_query_report": 1,
"label": "Sales Pipeline Analytics",
"link_count": 0,
"link_to": "Sales Pipeline Analytics",
"link_type": "Report",
"onboard": 0,
@ -153,6 +155,7 @@
"hidden": 0,
"is_query_report": 1,
"label": "Opportunity Summary by Sales Stage",
"link_count": 0,
"link_to": "Opportunity Summary by Sales Stage",
"link_type": "Report",
"onboard": 0,
@ -414,7 +417,7 @@
"type": "Link"
}
],
"modified": "2021-08-20 12:15:56.913092",
"modified": "2022-01-13 17:53:17.509844",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
@ -423,7 +426,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
"sequence_id": 7,
"sequence_id": 7.0,
"shortcuts": [
{
"color": "Blue",

View File

@ -1,18 +0,0 @@
[{
"account_name": "Debtors EUR",
"parent_account": "Accounts Receivable",
"account_type": "Receivable",
"account_currency": "EUR"
},
{
"account_name": "Creditors EUR",
"parent_account": "Accounts Payable",
"account_type": "Payable",
"account_currency": "EUR"
},
{
"account_name": "Paypal",
"parent_account": "Bank Accounts",
"account_type": "Bank",
"account_currency": "EUR"
}]

View File

@ -1,218 +0,0 @@
[
{
"address_line1": "254 Theotokopoulou Str.",
"address_type": "Office",
"city": "Larnaka",
"country": "Cyprus",
"links": [{"link_doctype": "Customer", "link_name": "Adaptas"}],
"phone": "23566775757"
},
{
"address_line1": "R Patr\u00e3o Caramelho 116",
"address_type": "Office",
"city": "Fajozes",
"country": "Portugal",
"links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}],
"phone": "23566775757"
},
{
"address_line1": "30 Fulford Road",
"address_type": "Office",
"city": "PENTRE-PIOD",
"country": "United Kingdom",
"links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}],
"phone": "23566775757"
},
{
"address_line1": "Schoenebergerstrasse 13",
"address_type": "Office",
"city": "Raschau",
"country": "Germany",
"links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}],
"phone": "23566775757"
},
{
"address_line1": "Hoheluftchaussee 43",
"address_type": "Office",
"city": "Kieritzsch",
"country": "Germany",
"links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}],
"phone": "23566775757"
},
{
"address_line1": "R Cimo Vila 6",
"address_type": "Office",
"city": "Rebordosa",
"country": "Portugal",
"links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}],
"phone": "23566775757"
},
{
"address_line1": "R 5 Outubro 9",
"address_type": "Office",
"city": "Quinta Nova S\u00e3o Domingos",
"country": "Portugal",
"links": [{"link_doctype": "Customer", "link_name": "Choices"}],
"phone": "23566775757"
},
{
"address_line1": "Avenida Macambira 953",
"address_type": "Office",
"city": "Goi\u00e2nia",
"country": "Brazil",
"links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}],
"phone": "23566775757"
},
{
"address_line1": "2342 Goyeau Ave",
"address_type": "Office",
"city": "Windsor",
"country": "Canada",
"links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}],
"phone": "23566775757"
},
{
"address_line1": "Laukaantie 82",
"address_type": "Office",
"city": "KOKKOLA",
"country": "Finland",
"links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}],
"phone": "23566775757"
},
{
"address_line1": "9 Brown Street",
"address_type": "Office",
"city": "PETERSHAM",
"country": "Australia",
"links": [{"link_doctype": "Customer", "link_name": "Fayva"}],
"phone": "23566775757"
},
{
"address_line1": "Via Donnalbina 41",
"address_type": "Office",
"city": "Cala Gonone",
"country": "Italy",
"links": [{"link_doctype": "Customer", "link_name": "Intelacard"}],
"phone": "23566775757"
},
{
"address_line1": "Liljerum Grenadj\u00e4rtorpet 69",
"address_type": "Office",
"city": "TOMTEBODA",
"country": "Sweden",
"links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}],
"phone": "23566775757"
},
{
"address_line1": "72 Bishopgate Street",
"address_type": "Office",
"city": "SEAHAM",
"country": "United Kingdom",
"links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}],
"phone": "23566775757"
},
{
"address_line1": "\u03a3\u03ba\u03b1\u03c6\u03af\u03b4\u03b9\u03b1 105",
"address_type": "Office",
"city": "\u03a0\u0391\u03a1\u0395\u039a\u039a\u039b\u0397\u03a3\u0399\u0391",
"country": "Cyprus",
"links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}],
"phone": "23566775757"
},
{
"address_line1": "Mellemvej 7",
"address_type": "Office",
"city": "Aabybro",
"country": "Denmark",
"links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}],
"phone": "23566775757"
},
{
"address_line1": "Plougg\u00e5rdsvej 98",
"address_type": "Office",
"city": "Karby",
"country": "Denmark",
"links": [{"link_doctype": "Customer", "link_name": "Netobill"}],
"phone": "23566775757"
},
{
"address_line1": "176 Michalakopoulou Street",
"address_type": "Office",
"city": "Agio Georgoudi",
"country": "Cyprus",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}]
},
{
"address_line1": "Fibichova 1102",
"address_type": "Office",
"city": "Kokor\u00edn",
"country": "Czech Republic",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}]
},
{
"address_line1": "Zahradn\u00ed 888",
"address_type": "Office",
"city": "Cecht\u00edn",
"country": "Czech Republic",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}]
},
{
"address_line1": "ul. Grochowska 94",
"address_type": "Office",
"city": "Warszawa",
"country": "Poland",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}]
},
{
"address_line1": "Norra Esplanaden 87",
"address_type": "Office",
"city": "HELSINKI",
"country": "Finland",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}]
},
{
"address_line1": "2038 Fallon Drive",
"address_type": "Office",
"city": "Dresden",
"country": "Canada",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}]
},
{
"address_line1": "77 cours Franklin Roosevelt",
"address_type": "Office",
"city": "MARSEILLE",
"country": "France",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}]
},
{
"address_line1": "ul. Tuwima Juliana 85",
"address_type": "Office",
"city": "\u0141\u00f3d\u017a",
"country": "Poland",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}]
},
{
"address_line1": "Gl. Sygehusvej 41",
"address_type": "Office",
"city": "Narsaq",
"country": "Greenland",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}]
},
{
"address_line1": "Gosposka ulica 50",
"address_type": "Office",
"city": "Nova Gorica",
"country": "Slovenia",
"phone": "23566775757",
"links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}]
}
]

View File

@ -1,18 +0,0 @@
[
{
"doctype": "Assessment Criteria",
"assessment_criteria": "Aptitude"
},
{
"doctype": "Assessment Criteria",
"assessment_criteria": "Application"
},
{
"doctype": "Assessment Criteria",
"assessment_criteria": "Understanding"
},
{
"doctype": "Assessment Criteria",
"assessment_criteria": "Knowledge"
}
]

View File

@ -1,58 +0,0 @@
[
{
"asset_name": "Macbook Pro - 1",
"item_code": "Computer",
"gross_purchase_amount": 100000,
"asset_owner": "Company",
"available_for_use_date": "2017-01-02",
"location": "Main Location"
},
{
"asset_name": "Macbook Air - 1",
"item_code": "Computer",
"gross_purchase_amount": 60000,
"asset_owner": "Company",
"available_for_use_date": "2017-10-02",
"location": "Avg Location"
},
{
"asset_name": "Conferrence Table",
"item_code": "Table",
"gross_purchase_amount": 30000,
"asset_owner": "Company",
"available_for_use_date": "2018-10-02",
"location": "Zany Location"
},
{
"asset_name": "Lunch Table",
"item_code": "Table",
"gross_purchase_amount": 20000,
"asset_owner": "Company",
"available_for_use_date": "2018-06-02",
"location": "Fletcher Location"
},
{
"asset_name": "ERPNext",
"item_code": "ERP",
"gross_purchase_amount": 100000,
"asset_owner": "Company",
"available_for_use_date": "2018-09-02",
"location":"Main Location"
},
{
"asset_name": "Chair 1",
"item_code": "Chair",
"gross_purchase_amount": 10000,
"asset_owner": "Company",
"available_for_use_date": "2018-07-02",
"location": "Zany Location"
},
{
"asset_name": "Chair 2",
"item_code": "Chair",
"gross_purchase_amount": 10000,
"asset_owner": "Company",
"available_for_use_date": "2018-07-02",
"location": "Avg Location"
}
]

View File

@ -1,38 +0,0 @@
[
{
"asset_category_name": "Furnitures",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 5,
"frequency_of_depreciation": 12,
"accounts": [{
"company_name": "Wind Power LLC",
"fixed_asset_account": "Furnitures and Fixtures - WPL",
"accumulated_depreciation_account": "Accumulated Depreciation - WPL",
"depreciation_expense_account": "Depreciation - WPL"
}]
},
{
"asset_category_name": "Electronic Equipments",
"depreciation_method": "Double Declining Balance",
"total_number_of_depreciations": 10,
"frequency_of_depreciation": 6,
"accounts": [{
"company_name": "Wind Power LLC",
"fixed_asset_account": "Electronic Equipments - WPL",
"accumulated_depreciation_account": "Accumulated Depreciation - WPL",
"depreciation_expense_account": "Depreciation - WPL"
}]
},
{
"asset_category_name": "Softwares",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 10,
"frequency_of_depreciation": 12,
"accounts": [{
"company_name": "Wind Power LLC",
"fixed_asset_account": "Softwares - WPL",
"accumulated_depreciation_account": "Accumulated Depreciation - WPL",
"depreciation_expense_account": "Depreciation - WPL"
}]
}
]

View File

@ -1,180 +0,0 @@
[
{
"item": "Bearing Assembly",
"items": [
{
"item_code": "Base Bearing Plate",
"qty": 1.0,
"rate": 15.0
},
{
"item_code": "Bearing Block",
"qty": 1.0,
"rate": 10.0
},
{
"item_code": "Bearing Collar",
"qty": 2.0,
"rate": 20.0
},
{
"item_code": "Bearing Pipe",
"qty": 1.0,
"rate": 15.0
},
{
"item_code": "Upper Bearing Plate",
"qty": 1.0,
"rate": 50.0
}
]
},
{
"item": "Wind Mill A Series",
"items": [
{
"item_code": "Base Bearing Plate",
"qty": 1.0,
"rate": 15.0
},
{
"item_code": "Base Plate",
"qty": 1.0,
"rate": 20.0
},
{
"item_code": "Bearing Block",
"qty": 1.0,
"rate": 10.0
},
{
"item_code": "Bearing Pipe",
"qty": 1.0,
"rate": 15.0
},
{
"item_code": "External Disc",
"qty": 1.0,
"rate": 45.0
},
{
"item_code": "Shaft",
"qty": 1.0,
"rate": 30.0
},
{
"item_code": "Wing Sheet",
"qty": 4.0,
"rate": 22.0
}
]
},
{
"item": "Wind MIll C Series",
"items": [
{
"item_code": "Base Plate",
"qty": 2.0,
"rate": 20.0
},
{
"item_code": "Internal Disc",
"qty": 1.0,
"rate": 33.0
},
{
"item_code": "External Disc",
"qty": 1.0,
"rate": 45.0
},
{
"item_code": "Bearing Assembly",
"qty": 1.0,
"rate": 130.0
},
{
"item_code": "Wing Sheet",
"qty": 3.0,
"rate": 22.0
}
]
},
{
"item": "Wind Turbine-S",
"with_operations": 1,
"operations": [
{
"operation": "Prepare Frame",
"time_in_mins": 30.0,
"workstation": "Drilling Machine 1"
},
{
"operation": "Setup Fixtures",
"time_in_mins": 15.0,
"workstation": "Assembly Station 1"
},
{
"operation": "Assembly Operation",
"time_in_mins": 30.0,
"workstation": "Assembly Station 1"
},
{
"operation": "Wiring",
"time_in_mins": 20.0,
"workstation": "Assembly Station 1"
},
{
"operation": "Testing",
"time_in_mins": 10.0,
"workstation": "Packing and Testing Station"
},
{
"operation": "Packing",
"time_in_mins": 25.0,
"workstation": "Packing and Testing Station"
}
],
"items": [
{
"item_code": "Base Bearing Plate",
"qty": 1.0,
"rate": 15.0
},
{
"item_code": "Base Plate",
"qty": 1.0,
"rate": 20.0
},
{
"item_code": "Bearing Collar",
"qty": 1.0,
"rate": 20.0
},
{
"item_code": "Blade Rib",
"qty": 1.0,
"rate": 10.0
},
{
"item_code": "Shaft",
"qty": 1.0,
"rate": 30.0
},
{
"item_code": "Wing Sheet",
"qty": 2.0,
"rate": 22.0
}
]
},
{
"item": "Base Plate",
"items": [
{
"item_code": "Base Plate Un Painted",
"qty": 1.0,
"rate": 16.0
}
]
}
]

View File

@ -1,164 +0,0 @@
[
{
"email_id": "JanVaclavik@example.com",
"first_name": "January",
"last_name": "V\u00e1clav\u00edk",
"links": [{"link_doctype": "Customer", "link_name": "Adaptas"}]
},
{
"email_id": "ChidumagaTobeolisa@example.com",
"first_name": "Chidumaga",
"last_name": "Tobeolisa",
"links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}]
},
{
"email_id": "JanaKubanova@example.com",
"first_name": "Jana",
"last_name": "Kub\u00e1\u0148ov\u00e1",
"links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}]
},
{
"email_id": "XuChaoXuan@example.com",
"first_name": "\u7d39\u8431",
"last_name": "\u4e8e",
"links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}]
},
{
"email_id": "OzlemVerwijmeren@example.com",
"first_name": "\u00d6zlem",
"last_name": "Verwijmeren",
"links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}]
},
{
"email_id": "HansRasmussen@example.com",
"first_name": "Hans",
"last_name": "Rasmussen",
"links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}]
},
{
"email_id": "SatomiShigeki@example.com",
"first_name": "Satomi",
"last_name": "Shigeki",
"links": [{"link_doctype": "Customer", "link_name": "Choices"}]
},
{
"email_id": "SimonVJessen@example.com",
"first_name": "Simon",
"last_name": "Jessen",
"links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}]
},
{
"email_id": "NeguaranShahsaah@example.com",
"first_name": "\u0646\u06af\u0627\u0631\u06cc\u0646",
"last_name": "\u0634\u0627\u0647 \u0633\u06cc\u0627\u0647",
"links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}]
},
{
"email_id": "Lom-AliBataev@example.com",
"first_name": "Lom-Ali",
"last_name": "Bataev",
"links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}]
},
{
"email_id": "VanNgocTien@example.com",
"first_name": "Ti\u00ean",
"last_name": "V\u0103n",
"links": [{"link_doctype": "Customer", "link_name": "Fayva"}]
},
{
"email_id": "QuimeyOsorioRuelas@example.com",
"first_name": "Quimey",
"last_name": "Osorio",
"links": [{"link_doctype": "Customer", "link_name": "Intelacard"}]
},
{
"email_id": "EdgardaSalcedoRaya@example.com",
"first_name": "Edgarda",
"last_name": "Salcedo",
"links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}]
},
{
"email_id": "HafsteinnBjarnarsonar@example.com",
"first_name": "Hafsteinn",
"last_name": "Bjarnarsonar",
"links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}]
},
{
"email_id": "\u0434\u0430\u043d\u0438\u0438\u043b@example.com",
"first_name": "\u0414\u0430\u043d\u0438\u0438\u043b",
"last_name": "\u041a\u043e\u043d\u043e\u0432\u0430\u043b\u043e\u0432",
"links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}]
},
{
"email_id": "SelmaMAndersen@example.com",
"first_name": "Selma",
"last_name": "Andersen",
"links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}]
},
{
"email_id": "LadislavKolaja@example.com",
"first_name": "Ladislav",
"last_name": "Kolaja",
"links": [{"link_doctype": "Customer", "link_name": "Netobill"}]
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}],
"email_id": "TewoldeAbaalom@example.com",
"first_name": "Tewolde",
"last_name": "Abaalom"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}],
"email_id": "LeilaFernandesRodrigues@example.com",
"first_name": "Leila",
"last_name": "Rodrigues"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}],
"email_id": "DmitryBulgakov@example.com",
"first_name": "Dmitry",
"last_name": "Bulgakov"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}],
"email_id": "HaiducWhitfoot@example.com",
"first_name": "Haiduc",
"last_name": "Whitfoot"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}],
"email_id": "SesseljaPetursdottir@example.com",
"first_name": "Sesselja",
"last_name": "P\u00e9tursd\u00f3ttir"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}],
"email_id": "HajdarPignar@example.com",
"first_name": "Hajdar",
"last_name": "Pignar"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}],
"email_id": "GustavaLorenzo@example.com",
"first_name": "Gustava",
"last_name": "Lorenzo"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}],
"email_id": "BethanyWood@example.com",
"first_name": "Bethany",
"last_name": "Wood"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}],
"email_id": "GlorianaBrownlock@example.com",
"first_name": "Gloriana",
"last_name": "Brownlock"
},
{
"links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}],
"email_id": "JensonFraser@gustr.com",
"first_name": "Jenson",
"last_name": "Fraser"
}
]

View File

@ -1,134 +0,0 @@
[
{
"doctype": "Course",
"course_name": "Communication Skiils",
"course_code": "BCA2040",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Object Oriented Programing - C++",
"course_code": "BCA2030",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Data Structures and Algorithm",
"course_code": "BCA2020",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Operating System",
"course_code": "BCA2010",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Digital Logic",
"course_code": "BCA1040",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Basic Mathematics",
"course_code": "BCA1030",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Programing in C",
"course_code": "BCA1020",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Fundamentals of IT & Programing",
"course_code": "BCA1010",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Microprocessor",
"course_code": "MCA4010",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Probability and Statistics",
"course_code": "MCA4020",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Programing in Java",
"course_code": "MCA4030",
"department": "Information Technology"
},
{
"doctype": "Course",
"course_name": "Communication Skills",
"course_code": "BBA 101",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Organizational Behavior",
"course_code": "BBA 102",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Business Environment",
"course_code": "BBA 103",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Legal and Regulatory Framework",
"course_code": "BBA 301",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Human Resource Management",
"course_code": "BBA 302",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Advertising and Sales",
"course_code": "BBA 304",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Entrepreneurship Management",
"course_code": "BBA 505",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Visual Merchandising",
"course_code": "BBR 504",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Warehouse Management",
"course_code": "BBR 505",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Store Operations and Job Knowledge",
"course_code": "BBR 501",
"department": "Management Studies"
},
{
"doctype": "Course",
"course_name": "Management Development and Skills",
"course_code": "BBA 602",
"department": "Management Studies"
}
]

View File

@ -1,30 +0,0 @@
[
{
"doctype": "Department",
"department_name": "Information Technology"
},
{
"doctype": "Department",
"department_name": "Physics"
},
{
"doctype": "Department",
"department_name": "Chemistry"
},
{
"doctype": "Department",
"department_name": "Biology"
},
{
"doctype": "Department",
"department_name": "Commerce"
},
{
"doctype": "Department",
"department_name": "English"
},
{
"doctype": "Department",
"department_name": "Management Studies"
}
]

Some files were not shown because too many files have changed in this diff Show More