Merge branch 'develop' into bug_invoice_creation_throws_typeerror
This commit is contained in:
commit
9432d8edd1
1
.flake8
1
.flake8
@ -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
|
||||
|
5
.github/helper/install.sh
vendored
5
.github/helper/install.sh
vendored
@ -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
|
||||
|
42
.github/labeler.yml
vendored
42
.github/labeler.yml
vendored
@ -1,53 +1,53 @@
|
||||
accounts:
|
||||
- 'erpnext/accounts/*'
|
||||
- 'erpnext/controllers/accounts_controller.py'
|
||||
- 'erpnext/controllers/taxes_and_totals.py'
|
||||
- erpnext/accounts/*
|
||||
- erpnext/controllers/accounts_controller.py
|
||||
- erpnext/controllers/taxes_and_totals.py
|
||||
|
||||
stock:
|
||||
- 'erpnext/stock/*'
|
||||
- 'erpnext/controllers/stock_controller.py'
|
||||
- 'erpnext/controllers/item_variant.py'
|
||||
- erpnext/stock/*
|
||||
- erpnext/controllers/stock_controller.py
|
||||
- erpnext/controllers/item_variant.py
|
||||
|
||||
assets:
|
||||
- 'erpnext/assets/*'
|
||||
- erpnext/assets/*
|
||||
|
||||
regional:
|
||||
- 'erpnext/regional/*'
|
||||
- erpnext/regional/*
|
||||
|
||||
selling:
|
||||
- 'erpnext/selling/*'
|
||||
- 'erpnext/controllers/selling_controller.py'
|
||||
- erpnext/selling/*
|
||||
- erpnext/controllers/selling_controller.py
|
||||
|
||||
buying:
|
||||
- 'erpnext/buying/*'
|
||||
- 'erpnext/controllers/buying_controller.py'
|
||||
- erpnext/buying/*
|
||||
- erpnext/controllers/buying_controller.py
|
||||
|
||||
support:
|
||||
- 'erpnext/support/*'
|
||||
- erpnext/support/*
|
||||
|
||||
POS:
|
||||
- 'pos*'
|
||||
- pos*
|
||||
|
||||
ecommerce:
|
||||
- 'erpnext/e_commerce/*'
|
||||
- erpnext/e_commerce/*
|
||||
|
||||
maintenance:
|
||||
- 'erpnext/maintenance/*'
|
||||
- erpnext/maintenance/*
|
||||
|
||||
manufacturing:
|
||||
- 'erpnext/manufacturing/*'
|
||||
- erpnext/manufacturing/*
|
||||
|
||||
crm:
|
||||
- 'erpnext/crm/*'
|
||||
- erpnext/crm/*
|
||||
|
||||
HR:
|
||||
- 'erpnext/hr/*'
|
||||
- erpnext/hr/*
|
||||
|
||||
payroll:
|
||||
- 'erpnext/payroll*'
|
||||
- erpnext/payroll*
|
||||
|
||||
projects:
|
||||
- 'erpnext/projects/*'
|
||||
- erpnext/projects/*
|
||||
|
||||
# Any python files modifed but no test files modified
|
||||
needs-tests:
|
||||
|
15
.github/workflows/server-tests-mariadb.yml
vendored
15
.github/workflows/server-tests-mariadb.yml
vendored
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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": []
|
||||
}
|
@ -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
|
@ -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:
|
||||
|
@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
@ -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": []
|
||||
}
|
@ -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)
|
@ -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
|
@ -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": []
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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'):
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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 = []
|
||||
|
@ -548,6 +548,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 +647,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({
|
||||
|
@ -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`
|
||||
|
@ -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) {
|
||||
|
@ -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'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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', [])
|
||||
|
||||
|
@ -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-.#####'
|
||||
|
@ -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"):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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.",
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
|
173
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
173
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal 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>
|
@ -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"
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -387,42 +387,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'))
|
||||
|
@ -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'))
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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: {
|
||||
|
@ -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
|
||||
}
|
@ -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()
|
@ -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")
|
||||
|
@ -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",
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
}
|
@ -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",
|
||||
|
@ -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 = []
|
||||
|
@ -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()
|
||||
@ -2156,3 +2159,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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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 = ''
|
||||
|
@ -204,7 +204,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 +251,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 +385,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,
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
||||
|
@ -4,19 +4,72 @@ import frappe
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_reset_default_field_value(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Purchase Receipt",
|
||||
"set_warehouse": "Warehouse 1",
|
||||
})
|
||||
def test_reset_default_field_value(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Purchase Receipt",
|
||||
"set_warehouse": "Warehouse 1",
|
||||
})
|
||||
|
||||
# Same values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, "Warehouse 1")
|
||||
# Same values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, "Warehouse 1")
|
||||
|
||||
# Mixed values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, None)
|
||||
# Mixed values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, None)
|
||||
|
||||
def test_reset_default_field_value_in_mfg_stock_entry(self):
|
||||
# manufacture stock entry with rows having blank source/target wh
|
||||
se = frappe.get_doc(
|
||||
doctype="Stock Entry",
|
||||
purpose="Manufacture",
|
||||
stock_entry_type="Manufacture",
|
||||
company="_Test Company",
|
||||
from_warehouse="_Test Warehouse - _TC",
|
||||
to_warehouse="_Test Warehouse 1 - _TC",
|
||||
items=[
|
||||
frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
|
||||
frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1)
|
||||
]
|
||||
)
|
||||
se.save()
|
||||
|
||||
# default fields must be untouched
|
||||
self.assertEqual(se.from_warehouse, "_Test Warehouse - _TC")
|
||||
self.assertEqual(se.to_warehouse, "_Test Warehouse 1 - _TC")
|
||||
|
||||
se.delete()
|
||||
|
||||
def test_reset_default_field_value_in_transfer_stock_entry(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Stock Entry",
|
||||
"purpose": "Material Receipt",
|
||||
"from_warehouse": "Warehouse 1",
|
||||
"to_warehouse": "Warehouse 2",
|
||||
})
|
||||
|
||||
# Same values
|
||||
doc.items = [
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
|
||||
]
|
||||
|
||||
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
self.assertEqual(doc.from_warehouse, "Warehouse 1")
|
||||
self.assertEqual(doc.to_warehouse, "Warehouse 2")
|
||||
|
||||
# Mixed values in source wh
|
||||
doc.items = [
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 3", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
|
||||
]
|
||||
|
||||
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
self.assertEqual(doc.from_warehouse, None)
|
||||
self.assertEqual(doc.to_warehouse, "Warehouse 2")
|
@ -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() {
|
||||
|
@ -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", ""))
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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"""
|
||||
|
@ -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({
|
||||
|
@ -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.",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
21
erpnext/crm/onboarding_step/crm_settings/crm_settings.json
Normal file
21
erpnext/crm/onboarding_step/crm_settings/crm_settings.json
Normal 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
|
||||
}
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
}]
|
@ -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"}]
|
||||
}
|
||||
]
|
@ -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"
|
||||
}
|
||||
]
|
@ -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"
|
||||
}
|
||||
]
|
@ -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"
|
||||
}]
|
||||
}
|
||||
]
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -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"
|
||||
}
|
||||
]
|
@ -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"
|
||||
}
|
||||
]
|
@ -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"
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -1,92 +0,0 @@
|
||||
[
|
||||
{
|
||||
"date_of_birth": "1982-01-03",
|
||||
"date_of_joining": "2001-10-10",
|
||||
"employee_name": "Diana Prince",
|
||||
"first_name": "Diana",
|
||||
"last_name": "Prince",
|
||||
"gender": "Female",
|
||||
"user_id": "DianaPrince@example.com"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1959-02-03",
|
||||
"date_of_joining": "1976-09-16",
|
||||
"employee_name": "Zatanna Zatara",
|
||||
"gender": "Female",
|
||||
"user_id": "ZatannaZatara@example.com",
|
||||
"first_name": "Zatanna",
|
||||
"last_name": "Zatara"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1982-03-03",
|
||||
"date_of_joining": "2000-06-16",
|
||||
"employee_name": "Holly Granger",
|
||||
"gender": "Female",
|
||||
"user_id": "HollyGranger@example.com",
|
||||
"first_name": "Holly",
|
||||
"last_name": "Granger"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1945-04-04",
|
||||
"date_of_joining": "1969-07-01",
|
||||
"employee_name": "Neptunia Aquaria",
|
||||
"gender": "Female",
|
||||
"user_id": "NeptuniaAquaria@example.com",
|
||||
"first_name": "Neptunia",
|
||||
"last_name": "Aquaria"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1978-05-03",
|
||||
"date_of_joining": "1999-12-24",
|
||||
"employee_name": "Arthur Curry",
|
||||
"gender": "Male",
|
||||
"user_id": "ArthurCurry@example.com",
|
||||
"first_name": "Arthur",
|
||||
"last_name": "Curry"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1964-06-03",
|
||||
"date_of_joining": "1981-08-05",
|
||||
"employee_name": "Thalia Al Ghul",
|
||||
"gender": "Female",
|
||||
"user_id": "ThaliaAlGhul@example.com",
|
||||
"first_name": "Thalia",
|
||||
"last_name": "Al Ghul"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1982-07-03",
|
||||
"date_of_joining": "2006-06-10",
|
||||
"employee_name": "Maxwell Lord",
|
||||
"gender": "Male",
|
||||
"user_id": "MaxwellLord@example.com",
|
||||
"first_name": "Maxwell",
|
||||
"last_name": "Lord"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1969-08-03",
|
||||
"date_of_joining": "1993-10-21",
|
||||
"employee_name": "Grace Choi",
|
||||
"gender": "Female",
|
||||
"user_id": "GraceChoi@example.com",
|
||||
"first_name": "Grace",
|
||||
"last_name": "Choi"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1982-09-03",
|
||||
"date_of_joining": "2005-09-06",
|
||||
"employee_name": "Vandal Savage",
|
||||
"gender": "Male",
|
||||
"user_id": "VandalSavage@example.com",
|
||||
"first_name": "Vandal",
|
||||
"last_name": "Savage"
|
||||
},
|
||||
{
|
||||
"date_of_birth": "1985-10-03",
|
||||
"date_of_joining": "2007-12-25",
|
||||
"employee_name": "Caitlin Snow",
|
||||
"gender": "Female",
|
||||
"user_id": "CaitlinSnow@example.com",
|
||||
"first_name": "Caitlin",
|
||||
"last_name": "Snow"
|
||||
}
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Grading Scale",
|
||||
"grading_scale_name": "Standard Grading",
|
||||
"description": "Standard Grading Scale",
|
||||
"intervals": [
|
||||
{"threshold": 100.0, "grade_code": "A", "grade_description": "Excellent"},
|
||||
{"threshold": 89.9, "grade_code": "B+", "grade_description": "Close to Excellence"},
|
||||
{"threshold": 80.0, "grade_code": "B", "grade_description": "Good"},
|
||||
{"threshold": 69.9, "grade_code": "C+", "grade_description": "Almost Good"},
|
||||
{"threshold": 60.0, "grade_code": "C", "grade_description": "Average"},
|
||||
{"threshold": 50.0, "grade_code": "D+", "grade_description": "Have to Work"},
|
||||
{"threshold": 40.0, "grade_code": "D", "grade_description": "Not met Baseline Expectations"},
|
||||
{"threshold": 0.0, "grade_code": "F", "grade_description": "Have to work a lot"}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,128 +0,0 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Eddie Jessup",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "William Dyer",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Alastor Moody",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Charles Xavier",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Cuthbert Calculus",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Reed Richards",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Urban Chronotis",
|
||||
"naming_series": "INS/",
|
||||
"department": "Physics"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "River Song",
|
||||
"naming_series": "INS/",
|
||||
"department": "Physics"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Yana",
|
||||
"naming_series": "INS/",
|
||||
"department": "Physics"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Neil Lasrado",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Deepshi Garg",
|
||||
"naming_series": "INS/",
|
||||
"department": "Chemistry"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Shubham Saxena",
|
||||
"naming_series": "INS/",
|
||||
"department": "Physics"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Rushabh Mehta",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Umari Syed",
|
||||
"naming_series": "INS/",
|
||||
"department": "Chemistry"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Aman Singh",
|
||||
"naming_series": "INS/",
|
||||
"department": "Physics"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Nabin",
|
||||
"naming_series": "INS/",
|
||||
"department": "Chemistry"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Kanchan Chauhan",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Valmik Jangla",
|
||||
"naming_series": "INS/",
|
||||
"department": "Chemistry"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Amit Jain",
|
||||
"naming_series": "INS/",
|
||||
"department": "Physics"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Shreyas P",
|
||||
"naming_series": "INS/",
|
||||
"department": "Chemistry"
|
||||
},
|
||||
{
|
||||
"doctype": "Instructor",
|
||||
"instructor_name": "Rohit",
|
||||
"naming_series": "INS/",
|
||||
"department": "Information Technology"
|
||||
}
|
||||
]
|
@ -1,493 +0,0 @@
|
||||
[
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Asiatic Solutions",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "For Upper Bearing",
|
||||
"image": "/assets/erpnext_demo/images/disc.png",
|
||||
"item_code": "Disc Collars",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Disc Collars"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Nan Duskin",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "CAST IRON, MCMASTER PART NO. 3710T13",
|
||||
"image": "/assets/erpnext_demo/images/bearing.jpg",
|
||||
"item_code": "Bearing Block",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Bearing Block"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": null,
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"description": "Wind Mill C Series for Commercial Use 18ft",
|
||||
"image": "/assets/erpnext_demo/images/wind-turbine-2.png",
|
||||
"item_code": "Wind MIll C Series",
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind MIll C Series"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": null,
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"description": "Wind Mill A Series for Home Use 9ft",
|
||||
"image": "/assets/erpnext_demo/images/wind-turbine.png",
|
||||
"item_code": "Wind Mill A Series",
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Mill A Series"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": null,
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->",
|
||||
"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
|
||||
"item_code": "Wind Turbine",
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Turbine",
|
||||
"has_variants": 1,
|
||||
"has_serial_no": 1,
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Size"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "HomeBase",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "1.5 in. Diameter x 36 in. Mild Steel Tubing",
|
||||
"image": null,
|
||||
"item_code": "Bearing Pipe",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Bearing Pipe"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "New World Realty",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "1/32 in. x 24 in. x 47 in. HDPE Opaque Sheet",
|
||||
"image": null,
|
||||
"item_code": "Wing Sheet",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Wing Sheet"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Eagle Hardware",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "3/16 in. x 6 in. x 6 in. Low Carbon Steel Plate",
|
||||
"image": null,
|
||||
"item_code": "Upper Bearing Plate",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Upper Bearing Plate"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Asiatic Solutions",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "Bearing Assembly",
|
||||
"image": null,
|
||||
"item_code": "Bearing Assembly",
|
||||
"item_group": "Sub Assemblies",
|
||||
"item_name": "Bearing Assembly"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "HomeBase",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood",
|
||||
"image": null,
|
||||
"item_code": "Base Plate",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Base Plate",
|
||||
"is_sub_contracted_item": 1
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Scott Ties",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "N/A",
|
||||
"image": null,
|
||||
"item_code": "Stand",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Stand"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Eagle Hardware",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "1 in. x 3 in. x 1 ft. Multipurpose Al Alloy Bar",
|
||||
"image": null,
|
||||
"item_code": "Bearing Collar",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Bearing Collar"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Eagle Hardware",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "1/4 in. x 6 in. x 6 in. Mild Steel Plate",
|
||||
"image": null,
|
||||
"item_code": "Base Bearing Plate",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Base Bearing Plate"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "HomeBase",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "15/32 in. x 4 ft. x 8 ft. 3-Ply Rtd Sheathing",
|
||||
"image": null,
|
||||
"item_code": "External Disc",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "External Disc"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Eagle Hardware",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "1.25 in. Diameter x 6 ft. Mild Steel Tubing",
|
||||
"image": null,
|
||||
"item_code": "Shaft",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Shaft"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "Ks Merchandise",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "1/2 in. x 2 ft. x 4 ft. Pine Plywood",
|
||||
"image": null,
|
||||
"item_code": "Blade Rib",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Blade Rib"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "HomeBase",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "For Bearing Collar",
|
||||
"image": null,
|
||||
"item_code": "Internal Disc",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Internal Disc"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": null,
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Small</p>",
|
||||
"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
|
||||
"item_code": "Wind Turbine-S",
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Turbine-S",
|
||||
"variant_of": "Wind Turbine",
|
||||
"valuation_rate": 300,
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Size",
|
||||
"attribute_value": "Small"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": null,
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Medium</p>",
|
||||
"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
|
||||
"item_code": "Wind Turbine-M",
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Turbine-M",
|
||||
"variant_of": "Wind Turbine",
|
||||
"valuation_rate": 300,
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Size",
|
||||
"attribute_value": "Medium"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": null,
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Large</p>",
|
||||
"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
|
||||
"item_code": "Wind Turbine-L",
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Turbine-L",
|
||||
"variant_of": "Wind Turbine",
|
||||
"valuation_rate": 300,
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Size",
|
||||
"attribute_value": "Large"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"is_stock_item": 0,
|
||||
"description": "Wind Mill A Series with Spare Bearing",
|
||||
"item_code": "Wind Mill A Series with Spare Bearing",
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Mill A Series with Spare Bearing"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_supplier": "HomeBase",
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood",
|
||||
"image": null,
|
||||
"item_code": "Base Plate Un Painted",
|
||||
"item_group": "Raw Material",
|
||||
"item_name": "Base Plate Un Painted"
|
||||
},
|
||||
{
|
||||
"is_fixed_asset": 1,
|
||||
"asset_category": "Furnitures",
|
||||
"is_stock_item": 0,
|
||||
"description": "Table",
|
||||
"item_code": "Table",
|
||||
"item_name": "Table",
|
||||
"item_group": "Products"
|
||||
},
|
||||
{
|
||||
"is_fixed_asset": 1,
|
||||
"asset_category": "Furnitures",
|
||||
"is_stock_item": 0,
|
||||
"description": "Chair",
|
||||
"item_code": "Chair",
|
||||
"item_name": "Chair",
|
||||
"item_group": "Products"
|
||||
},
|
||||
{
|
||||
"is_fixed_asset": 1,
|
||||
"asset_category": "Electronic Equipments",
|
||||
"is_stock_item": 0,
|
||||
"description": "Computer",
|
||||
"item_code": "Computer",
|
||||
"item_name": "Computer",
|
||||
"item_group": "Products"
|
||||
},
|
||||
{
|
||||
"is_fixed_asset": 1,
|
||||
"asset_category": "Electronic Equipments",
|
||||
"is_stock_item": 0,
|
||||
"description": "Mobile",
|
||||
"item_code": "Mobile",
|
||||
"item_name": "Mobile",
|
||||
"item_group": "Products"
|
||||
},
|
||||
{
|
||||
"is_fixed_asset": 1,
|
||||
"asset_category": "Softwares",
|
||||
"is_stock_item": 0,
|
||||
"description": "ERP",
|
||||
"item_code": "ERP",
|
||||
"item_name": "ERP",
|
||||
"item_group": "All Item Groups"
|
||||
},
|
||||
{
|
||||
"is_fixed_asset": 1,
|
||||
"asset_category": "Softwares",
|
||||
"is_stock_item": 0,
|
||||
"description": "Autocad",
|
||||
"item_code": "Autocad",
|
||||
"item_name": "Autocad",
|
||||
"item_group": "All Item Groups"
|
||||
},
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"valuation_rate": 200,
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "Stores"
|
||||
}
|
||||
],
|
||||
"description": "Corrugated Box",
|
||||
"item_code": "Corrugated Box",
|
||||
"item_name": "Corrugated Box",
|
||||
"item_group": "All Item Groups"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"is_stock_item": 1,
|
||||
"description": "OnePlus 6",
|
||||
"item_code": "OnePlus 6",
|
||||
"item_name": "OnePlus 6",
|
||||
"item_group": "Products",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"is_stock_item": 1,
|
||||
"description": "OnePlus 6T",
|
||||
"item_code": "OnePlus 6T",
|
||||
"item_name": "OnePlus 6T",
|
||||
"item_group": "Products",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"is_stock_item": 1,
|
||||
"description": "Xiaomi Poco F1",
|
||||
"item_code": "Xiaomi Poco F1",
|
||||
"item_name": "Xiaomi Poco F1",
|
||||
"item_group": "Products",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"is_stock_item": 1,
|
||||
"description": "Iphone XS",
|
||||
"item_code": "Iphone XS",
|
||||
"item_name": "Iphone XS",
|
||||
"item_group": "Products",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"is_stock_item": 1,
|
||||
"description": "Samsung Galaxy S9",
|
||||
"item_code": "Samsung Galaxy S9",
|
||||
"item_name": "Samsung Galaxy S9",
|
||||
"item_group": "Products",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "Finished Goods"
|
||||
}
|
||||
],
|
||||
"is_stock_item": 1,
|
||||
"description": "Sony Bluetooth Headphone",
|
||||
"item_code": "Sony Bluetooth Headphone",
|
||||
"item_name": "Sony Bluetooth Headphone",
|
||||
"item_group": "Products",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"is_stock_item": 0,
|
||||
"description": "Samsung Phone Repair",
|
||||
"item_code": "Samsung Phone Repair",
|
||||
"item_name": "Samsung Phone Repair",
|
||||
"item_group": "Services",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"is_stock_item": 0,
|
||||
"description": "OnePlus Phone Repair",
|
||||
"item_code": "OnePlus Phone Repair",
|
||||
"item_name": "OnePlus Phone Repair",
|
||||
"item_group": "Services",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"is_stock_item": 0,
|
||||
"description": "Xiaomi Phone Repair",
|
||||
"item_code": "Xiaomi Phone Repair",
|
||||
"item_name": "Xiaomi Phone Repair",
|
||||
"item_group": "Services",
|
||||
"domain": "Retail"
|
||||
},
|
||||
{
|
||||
"is_stock_item": 0,
|
||||
"description": "Apple Phone Repair",
|
||||
"item_code": "Apple Phone Repair",
|
||||
"item_name": "Apple Phone Repair",
|
||||
"item_group": "Services",
|
||||
"domain": "Retail"
|
||||
}
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user