diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 4b1147e79f9..5b607a99406 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -11,4 +11,4 @@ jobs:
- name: curl
run: |
apk add curl bash
- curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
+ curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index 92a19621d15..f8abb6c7741 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -86,4 +86,27 @@ jobs:
cd ~/frappe-bench/
wget https://erpnext.com/files/v10-erpnext.sql.gz
bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
+
+ git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
+ git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
+
+ for version in $(seq 12 13)
+ do
+ echo "Updating to v$version"
+ branch_name="version-$version"
+
+ git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
+ git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
+
+ git -C "apps/frappe" checkout -q -f $branch_name
+ git -C "apps/erpnext" checkout -q -f $branch_name
+
+ bench setup requirements --python
+ bench --site test_site migrate
+ done
+
+
+ echo "Updating to latest version"
+ git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
+ git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
bench --site test_site migrate
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index 05caafe1c47..3596c340175 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -81,7 +81,7 @@ def add_suffix_if_duplicate(account_name, account_number, accounts):
def identify_is_group(child):
if child.get("is_group"):
is_group = child.get("is_group")
- elif len(set(child.keys()) - set(["account_type", "root_type", "is_group", "tax_rate", "account_number"])):
+ elif len(set(child.keys()) - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])):
is_group = 1
else:
is_group = 0
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 1e983b1d429..60015f6ec88 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -58,7 +58,8 @@ class GLEntry(Document):
# Update outstanding amt on against voucher
if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees']
- and self.against_voucher and self.flags.update_outstanding == 'Yes'):
+ and self.against_voucher and self.flags.update_outstanding == 'Yes'
+ and not frappe.flags.is_reverse_depr_entry):
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index e568a827617..f3a0bdbec49 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -58,7 +58,10 @@ class JournalEntry(AccountsController):
if not frappe.flags.in_import:
self.validate_total_debit_and_credit()
- self.validate_against_jv()
+ if not frappe.flags.is_reverse_depr_entry:
+ self.validate_against_jv()
+ self.validate_stock_accounts()
+
self.validate_reference_doc()
if self.docstatus == 0:
self.set_against_account()
@@ -69,7 +72,6 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
- self.validate_stock_accounts()
if self.docstatus == 0:
self.apply_tax_withholding()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 9b4a91d4e96..8bbe3db914f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -389,7 +389,7 @@ class PaymentEntry(AccountsController):
invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
- for key, allocated_amount in iteritems(invoice_payment_amount_map):
+ for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1):
if not invoice_paid_amount_map.get(key):
frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1]))
@@ -407,7 +407,7 @@ class PaymentEntry(AccountsController):
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
else:
if allocated_amount > outstanding:
- frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
+ frappe.throw(_('Row #{0}: Cannot allocate more than {1} against payment term {2}').format(idx, outstanding, key[0]))
if allocated_amount and outstanding:
frappe.db.sql("""
@@ -1053,12 +1053,6 @@ def get_outstanding_reference_documents(args):
party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.get_cached_value('Company', args.get("company"), "default_currency")
- # Get negative outstanding sales /purchase invoices
- negative_outstanding_invoices = []
- if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
- negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"),
- args.get("party_account"), args.get("company"), party_account_currency, company_currency)
-
# Get positive outstanding sales /purchase invoices/ Fees
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
@@ -1105,6 +1099,12 @@ def get_outstanding_reference_documents(args):
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args)
+ # Get negative outstanding sales /purchase invoices
+ negative_outstanding_invoices = []
+ if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
+ negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"),
+ args.get("party_account"), party_account_currency, company_currency, condition=condition)
+
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data:
@@ -1137,22 +1137,26 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
'invoice_amount': flt(d.invoice_amount),
'outstanding_amount': flt(d.outstanding_amount),
'payment_amount': payment_term.payment_amount,
- 'payment_term': payment_term.payment_term,
- 'allocated_amount': payment_term.outstanding
+ 'payment_term': payment_term.payment_term
}))
+ outstanding_invoices_after_split = []
if invoice_ref_based_on_payment_terms:
for idx, ref in invoice_ref_based_on_payment_terms.items():
- voucher_no = outstanding_invoices[idx]['voucher_no']
- voucher_type = outstanding_invoices[idx]['voucher_type']
+ voucher_no = ref[0]['voucher_no']
+ voucher_type = ref[0]['voucher_type']
- frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format(
+ frappe.msgprint(_("Spliting {} {} into {} row(s) as per Payment Terms").format(
voucher_type, voucher_no, len(ref)), alert=True)
- outstanding_invoices.pop(idx - 1)
- outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
+ outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
- return outstanding_invoices
+ existing_row = list(filter(lambda x: x.get('voucher_no') == voucher_no, outstanding_invoices))
+ index = outstanding_invoices.index(existing_row[0])
+ outstanding_invoices.pop(index)
+
+ outstanding_invoices_after_split += outstanding_invoices
+ return outstanding_invoices_after_split
def get_orders_to_be_billed(posting_date, party_type, party,
company, party_account_currency, company_currency, cost_center=None, filters=None):
@@ -1219,7 +1223,7 @@ def get_orders_to_be_billed(posting_date, party_type, party,
return order_list
def get_negative_outstanding_invoices(party_type, party, party_account,
- company, party_account_currency, company_currency, cost_center=None):
+ party_account_currency, company_currency, cost_center=None, condition=None):
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
@@ -1241,19 +1245,21 @@ def get_negative_outstanding_invoices(party_type, party, party_account,
`tab{voucher_type}`
where
{party_type} = %s and {party_account} = %s and docstatus = 1 and
- company = %s and outstanding_amount < 0
+ outstanding_amount < 0
{supplier_condition}
+ {condition}
order by
posting_date, name
""".format(**{
"supplier_condition": supplier_condition,
+ "condition": condition,
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"party_account": "debit_to" if party_type == "Customer" else "credit_to",
"cost_center": cost_center
- }), (party, party_account, company), as_dict=True)
+ }), (party, party_account), as_dict=True)
@frappe.whitelist()
diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js
index aa373bc2fcc..9074defa577 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.js
+++ b/erpnext/accounts/doctype/payment_order/payment_order.js
@@ -10,6 +10,9 @@ frappe.ui.form.on('Payment Order', {
}
}
});
+
+ frm.set_df_property('references', 'cannot_add_rows', true);
+ frm.set_df_property('references', 'cannot_delete_rows', true);
},
refresh: function(frm) {
if (frm.doc.docstatus == 0) {
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 412833bd192..ad5a84094ea 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -4,9 +4,14 @@
frappe.provide("erpnext.accounts");
erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationController extends frappe.ui.form.Controller {
onload() {
- var me = this;
+ const default_company = frappe.defaults.get_default('company');
+ this.frm.set_value('company', default_company);
- this.frm.set_query("party_type", function() {
+ this.frm.set_value('party_type', '');
+ this.frm.set_value('party', '');
+ this.frm.set_value('receivable_payable_account', '');
+
+ this.frm.set_query("party_type", () => {
return {
"filters": {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
@@ -14,44 +19,30 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
}
});
- this.frm.set_query('receivable_payable_account', function() {
- check_mandatory(me.frm);
+ this.frm.set_query('receivable_payable_account', () => {
return {
filters: {
- "company": me.frm.doc.company,
+ "company": this.frm.doc.company,
"is_group": 0,
- "account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
+ "account_type": frappe.boot.party_account_types[this.frm.doc.party_type]
}
};
});
- this.frm.set_query('bank_cash_account', function() {
- check_mandatory(me.frm, true);
+ this.frm.set_query('bank_cash_account', () => {
return {
filters:[
- ['Account', 'company', '=', me.frm.doc.company],
+ ['Account', 'company', '=', this.frm.doc.company],
['Account', 'is_group', '=', 0],
['Account', 'account_type', 'in', ['Bank', 'Cash']]
]
};
});
-
- this.frm.set_value('party_type', '');
- this.frm.set_value('party', '');
- this.frm.set_value('receivable_payable_account', '');
-
- var check_mandatory = (frm, only_company=false) => {
- var title = __("Mandatory");
- if (only_company && !frm.doc.company) {
- frappe.throw({message: __("Please Select a Company First"), title: title});
- } else if (!frm.doc.company || !frm.doc.party_type) {
- frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
- }
- };
}
refresh() {
this.frm.disable_save();
+
this.frm.set_df_property('invoices', 'cannot_delete_rows', true);
this.frm.set_df_property('payments', 'cannot_delete_rows', true);
this.frm.set_df_property('allocation', 'cannot_delete_rows', true);
@@ -85,76 +76,92 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
}
company() {
- var me = this;
+ this.frm.set_value('party', '');
this.frm.set_value('receivable_payable_account', '');
- me.frm.clear_table("allocation");
- me.frm.clear_table("invoices");
- me.frm.clear_table("payments");
- me.frm.refresh_fields();
- me.frm.trigger('party');
+ }
+
+ party_type() {
+ this.frm.set_value('party', '');
}
party() {
- var me = this;
- if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
+ this.frm.set_value('receivable_payable_account', '');
+ this.frm.trigger("clear_child_tables");
+
+ if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) {
return frappe.call({
method: "erpnext.accounts.party.get_party_account",
args: {
- company: me.frm.doc.company,
- party_type: me.frm.doc.party_type,
- party: me.frm.doc.party
+ company: this.frm.doc.company,
+ party_type: this.frm.doc.party_type,
+ party: this.frm.doc.party
},
- callback: function(r) {
+ callback: (r) => {
if (!r.exc && r.message) {
- me.frm.set_value("receivable_payable_account", r.message);
+ this.frm.set_value("receivable_payable_account", r.message);
}
- me.frm.refresh();
+ this.frm.refresh();
+
}
});
}
}
+ receivable_payable_account() {
+ this.frm.trigger("clear_child_tables");
+ this.frm.refresh();
+ }
+
+ clear_child_tables() {
+ this.frm.clear_table("invoices");
+ this.frm.clear_table("payments");
+ this.frm.clear_table("allocation");
+ this.frm.refresh_fields();
+ }
+
get_unreconciled_entries() {
- var me = this;
+ this.frm.clear_table("allocation");
return this.frm.call({
- doc: me.frm.doc,
+ doc: this.frm.doc,
method: 'get_unreconciled_entries',
- callback: function(r, rt) {
- if (!(me.frm.doc.payments.length || me.frm.doc.invoices.length)) {
- frappe.throw({message: __("No invoice and payment records found for this party")});
+ callback: () => {
+ if (!(this.frm.doc.payments.length || this.frm.doc.invoices.length)) {
+ frappe.throw({message: __("No Unreconciled Invoices and Payments found for this party and account")});
+ } else if (!(this.frm.doc.invoices.length)) {
+ frappe.throw({message: __("No Outstanding Invoices found for this party")});
+ } else if (!(this.frm.doc.payments.length)) {
+ frappe.throw({message: __("No Unreconciled Payments found for this party")});
}
- me.frm.refresh();
+ this.frm.refresh();
}
});
}
allocate() {
- var me = this;
- let payments = me.frm.fields_dict.payments.grid.get_selected_children();
+ let payments = this.frm.fields_dict.payments.grid.get_selected_children();
if (!(payments.length)) {
- payments = me.frm.doc.payments;
+ payments = this.frm.doc.payments;
}
- let invoices = me.frm.fields_dict.invoices.grid.get_selected_children();
+ let invoices = this.frm.fields_dict.invoices.grid.get_selected_children();
if (!(invoices.length)) {
- invoices = me.frm.doc.invoices;
+ invoices = this.frm.doc.invoices;
}
- return me.frm.call({
- doc: me.frm.doc,
+ return this.frm.call({
+ doc: this.frm.doc,
method: 'allocate_entries',
args: {
payments: payments,
invoices: invoices
},
- callback: function() {
- me.frm.refresh();
+ callback: () => {
+ this.frm.refresh();
}
});
}
reconcile() {
- var me = this;
- var show_dialog = me.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account);
+ var show_dialog = this.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account);
if (show_dialog && show_dialog.length) {
@@ -186,10 +193,10 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
label: __("Difference Account"),
fieldname: 'difference_account',
reqd: 1,
- get_query: function() {
+ get_query: () => {
return {
filters: {
- company: me.frm.doc.company,
+ company: this.frm.doc.company,
is_group: 0
}
}
@@ -203,7 +210,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
}]
},
],
- primary_action: function() {
+ primary_action: () => {
const args = dialog.get_values()["allocation"];
args.forEach(d => {
@@ -211,7 +218,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
"difference_account", d.difference_account);
});
- me.reconcile_payment_entries();
+ this.reconcile_payment_entries();
dialog.hide();
},
primary_action_label: __('Reconcile Entries')
@@ -237,15 +244,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
}
reconcile_payment_entries() {
- var me = this;
-
return this.frm.call({
- doc: me.frm.doc,
+ doc: this.frm.doc,
method: 'reconcile',
- callback: function(r, rt) {
- me.frm.clear_table("allocation");
- me.frm.refresh_fields();
- me.frm.refresh();
+ callback: () => {
+ this.frm.clear_table("allocation");
+ this.frm.refresh();
}
});
}
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 4f26ed43db7..28bd10283e7 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -114,6 +114,8 @@ class POSInvoiceMergeLog(Document):
def merge_pos_invoice_into(self, invoice, data):
items, payments, taxes = [], [], []
loyalty_amount_sum, loyalty_points_sum = 0, 0
+ rounding_adjustment, base_rounding_adjustment = 0, 0
+ rounded_total, base_rounded_total = 0, 0
for doc in data:
map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
@@ -162,6 +164,11 @@ class POSInvoiceMergeLog(Document):
found = True
if not found:
payments.append(payment)
+ rounding_adjustment += doc.rounding_adjustment
+ rounded_total += doc.rounded_total
+ base_rounding_adjustment += doc.rounding_adjustment
+ base_rounded_total += doc.rounded_total
+
if loyalty_points_sum:
invoice.redeem_loyalty_points = 1
@@ -171,6 +178,10 @@ class POSInvoiceMergeLog(Document):
invoice.set('items', items)
invoice.set('payments', payments)
invoice.set('taxes', taxes)
+ invoice.set('rounding_adjustment',rounding_adjustment)
+ invoice.set('rounding_adjustment',base_rounding_adjustment)
+ invoice.set('base_rounded_total',base_rounded_total)
+ invoice.set('rounded_total',rounded_total)
invoice.additional_discount_percentage = 0
invoice.discount_amount = 0.0
invoice.taxes_and_charges = None
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index 9003af56a5d..7d8f3562c8c 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -2,11 +2,11 @@
// For license information, please see license.txt
let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
-let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
+let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "asset_naming_series",
"default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
"serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
"deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
- "web_long_description", "hub_sync_id"]
+ "web_long_description"]
frappe.ui.form.on('POS Settings', {
onload: function(frm) {
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 0637fdaef02..ef44b414761 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -29,6 +29,9 @@ def get_pricing_rules(args, doc=None):
pricing_rules = []
values = {}
+ if not frappe.db.exists('Pricing Rule', {'disable': 0, args.transaction_type: 1}):
+ return
+
for apply_on in ['Item Code', 'Item Group', 'Brand']:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 40ad7b7b5c8..cd204ba523b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -37,7 +37,7 @@ from erpnext.assets.doctype.asset.depreciation import (
get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain,
- post_depreciation_entries,
+ make_depreciation_entry,
)
from erpnext.controllers.selling_controller import SellingController
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
@@ -934,6 +934,7 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", None)
if asset.calculate_depreciation:
+ self.reverse_depreciation_entry_made_after_sale(asset)
self.reset_depreciation_schedule(asset)
else:
@@ -997,22 +998,20 @@ class SalesInvoice(SellingController):
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
- asset.prepare_depreciation_data(self.posting_date)
+ asset.prepare_depreciation_data(date_of_sale=self.posting_date)
asset.save()
- post_depreciation_entries(self.posting_date)
+ make_depreciation_entry(asset.name, self.posting_date)
def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
- asset.prepare_depreciation_data()
+ asset.prepare_depreciation_data(date_of_return=self.posting_date)
self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
- self.delete_depreciation_entry_made_after_sale(asset)
-
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
'Asset Repair',
@@ -1026,7 +1025,7 @@ class SalesInvoice(SellingController):
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
- def delete_depreciation_entry_made_after_sale(self, asset):
+ def reverse_depreciation_entry_made_after_sale(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
@@ -1041,11 +1040,19 @@ class SalesInvoice(SellingController):
row += 1
if schedule.schedule_date == posting_date_of_original_invoice:
- if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice):
+ if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice) \
+ or self.sale_happens_in_the_future(posting_date_of_original_invoice):
+
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
+ frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
+ frappe.flags.is_reverse_depr_entry = False
+ asset.flags.ignore_validate_update_after_submit = True
+ schedule.journal_entry = None
+ asset.save()
+
def get_posting_date_of_sales_invoice(self):
return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date')
@@ -1060,6 +1067,12 @@ class SalesInvoice(SellingController):
return True
return False
+ def sale_happens_in_the_future(self, posting_date_of_original_invoice):
+ if posting_date_of_original_invoice > getdate():
+ return True
+
+ return False
+
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):
@@ -1975,22 +1988,23 @@ def update_multi_mode_option(doc, pos_profile):
def append_payment(payment_mode):
payment = doc.append('payments', {})
payment.default = payment_mode.default
- payment.mode_of_payment = payment_mode.parent
+ payment.mode_of_payment = payment_mode.mop
payment.account = payment_mode.default_account
payment.type = payment_mode.type
doc.set('payments', [])
invalid_modes = []
- for pos_payment_method in pos_profile.get('payments'):
- pos_payment_method = pos_payment_method.as_dict()
+ mode_of_payments = [d.mode_of_payment for d in pos_profile.get('payments')]
+ mode_of_payments_info = get_mode_of_payments_info(mode_of_payments, doc.company)
- payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
+ for row in pos_profile.get('payments'):
+ payment_mode = mode_of_payments_info.get(row.mode_of_payment)
if not payment_mode:
- invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
+ invalid_modes.append(get_link_to_form("Mode of Payment", row.mode_of_payment))
continue
- payment_mode[0].default = pos_payment_method.default
- append_payment(payment_mode[0])
+ payment_mode.default = row.default
+ append_payment(payment_mode)
if invalid_modes:
if invalid_modes == 1:
@@ -2006,6 +2020,24 @@ def get_all_mode_of_payments(doc):
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
{'company': doc.company}, as_dict=1)
+def get_mode_of_payments_info(mode_of_payments, company):
+ data = frappe.db.sql(
+ """
+ select
+ mpa.default_account, mpa.parent as mop, mp.type as type
+ from
+ `tabMode of Payment Account` mpa,`tabMode of Payment` mp
+ where
+ mpa.parent = mp.name and
+ mpa.company = %s and
+ mp.enabled = 1 and
+ mp.name in (%s)
+ group by
+ mp.name
+ """, (company, mode_of_payments), as_dict=1)
+
+ return {row.get('mop'): row for row in data}
+
def get_mode_of_payment_info(mode_of_payment, company):
return frappe.db.sql("""
select mpa.default_account, mpa.parent, mp.type as type
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 56de3c62920..262c0834049 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2237,9 +2237,9 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
enable_discount_accounting(enable=0)
- def test_asset_depreciation_on_sale(self):
+ def test_asset_depreciation_on_sale_with_pro_rata(self):
"""
- Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on Sept 30.
+ Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
"""
create_asset_data()
@@ -2252,7 +2252,7 @@ class TestSalesInvoice(unittest.TestCase):
expected_values = [
["2020-06-30", 1311.48, 1311.48],
["2021-06-30", 20000.0, 21311.48],
- ["2021-09-30", 3966.76, 25278.24]
+ ["2021-09-30", 5041.1, 26352.58]
]
for i, schedule in enumerate(asset.schedules):
@@ -2261,6 +2261,59 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry)
+ def test_asset_depreciation_on_sale_without_pro_rata(self):
+ """
+ Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale.
+ """
+
+ create_asset_data()
+ asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1,
+ available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1)
+
+ post_depreciation_entries(getdate("2021-09-30"))
+
+ create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-12-31"))
+ asset.load_from_db()
+
+ expected_values = [
+ ["2020-12-31", 30000, 30000],
+ ["2021-12-31", 30000, 60000]
+ ]
+
+ for i, schedule in enumerate(asset.schedules):
+ self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
+ self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
+ self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
+ self.assertTrue(schedule.journal_entry)
+
+ def test_depreciation_on_return_of_sold_asset(self):
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
+ create_asset_data()
+ asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1)
+ post_depreciation_entries(getdate("2021-09-30"))
+
+ si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30"))
+ return_si = make_return_doc("Sales Invoice", si.name)
+ return_si.submit()
+ 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]
+ ]
+
+ for i, schedule in enumerate(asset.schedules):
+ self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
+ self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
+ self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
+ self.assertEqual(schedule.journal_entry, schedule.journal_entry)
+
def test_sales_invoice_against_supplier(self):
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index de9550233f9..050c3e7ea84 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -502,9 +502,11 @@ class Subscription(Document):
# Check invoice dates and make sure it doesn't have outstanding invoices
return getdate() >= getdate(self.current_invoice_start)
- def is_current_invoice_generated(self):
+ def is_current_invoice_generated(self, _current_start_date=None, _current_end_date=None):
invoice = self.get_current_invoice()
- _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True)
+
+ if not (_current_start_date and _current_end_date):
+ _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True)
if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date):
return True
@@ -523,7 +525,9 @@ class Subscription(Document):
if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice():
self.update_subscription_period(add_days(self.current_invoice_end, 1))
- if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
+ if not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \
+ and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
+
prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
self.generate_invoice(prorate)
@@ -559,14 +563,17 @@ class Subscription(Document):
else:
self.set_status_grace_period()
+ if getdate() > getdate(self.current_invoice_end):
+ self.update_subscription_period(add_days(self.current_invoice_end, 1))
+
# Generate invoices periodically even if current invoice are unpaid
- if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice()
- or self.is_prepaid_to_invoice()):
+ if self.generate_new_invoices_past_due_date and not \
+ self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \
+ and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
+
prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
self.generate_invoice(prorate)
- if getdate() > getdate(self.current_invoice_end):
- self.update_subscription_period(add_days(self.current_invoice_end, 1))
@staticmethod
def is_paid(invoice):
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index c36f3cb201b..51621820763 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -58,15 +58,24 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
pan_no = ''
parties = []
party_type, party = get_party_details(inv)
+ has_pan_field = frappe.get_meta(party_type).has_field("pan")
if not tax_withholding_category:
- tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan'])
+ if has_pan_field:
+ fields = ['tax_withholding_category', 'pan']
+ else:
+ fields = ['tax_withholding_category']
+
+ tax_withholding_details = frappe.db.get_value(party_type, party, fields, as_dict=1)
+
+ tax_withholding_category = tax_withholding_details.get('tax_withholding_category')
+ pan_no = tax_withholding_details.get('pan')
if not tax_withholding_category:
return
# if tax_withholding_category passed as an argument but not pan_no
- if not pan_no:
+ if not pan_no and has_pan_field:
pan_no = frappe.db.get_value(party_type, party, 'pan')
# Get others suppliers with the same PAN No
@@ -174,6 +183,7 @@ def get_lower_deduction_certificate(tax_details, pan_no):
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
{
'pan_no': pan_no,
+ 'tax_withholding_category': tax_details.tax_withholding_category,
'valid_from': ('>=', tax_details.from_date),
'valid_upto': ('<=', tax_details.to_date)
}, 'name')
diff --git a/erpnext/hub/__init__.py b/erpnext/accounts/print_format_field_template/__init__.py
similarity index 100%
rename from erpnext/hub/__init__.py
rename to erpnext/accounts/print_format_field_template/__init__.py
diff --git a/erpnext/hub_node/doctype/__init__.py b/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/__init__.py
similarity index 100%
rename from erpnext/hub_node/doctype/__init__.py
rename to erpnext/accounts/print_format_field_template/purchase_invoice_taxes/__init__.py
diff --git a/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/purchase_invoice_taxes.json b/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/purchase_invoice_taxes.json
new file mode 100644
index 00000000000..f525f7b8d42
--- /dev/null
+++ b/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/purchase_invoice_taxes.json
@@ -0,0 +1,16 @@
+{
+ "creation": "2021-10-19 18:06:53.083133",
+ "docstatus": 0,
+ "doctype": "Print Format Field Template",
+ "document_type": "Purchase Invoice",
+ "field": "taxes",
+ "idx": 0,
+ "modified": "2021-10-19 18:06:53.083133",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Purchase Invoice Taxes",
+ "owner": "Administrator",
+ "standard": 1,
+ "template": "",
+ "template_file": "templates/print_formats/includes/taxes_and_charges.html"
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/__init__.py b/erpnext/accounts/print_format_field_template/sales_invoice_taxes/__init__.py
similarity index 100%
rename from erpnext/hub_node/doctype/hub_tracked_item/__init__.py
rename to erpnext/accounts/print_format_field_template/sales_invoice_taxes/__init__.py
diff --git a/erpnext/accounts/print_format_field_template/sales_invoice_taxes/sales_invoice_taxes.json b/erpnext/accounts/print_format_field_template/sales_invoice_taxes/sales_invoice_taxes.json
new file mode 100644
index 00000000000..8ce62a8b26f
--- /dev/null
+++ b/erpnext/accounts/print_format_field_template/sales_invoice_taxes/sales_invoice_taxes.json
@@ -0,0 +1,16 @@
+{
+ "creation": "2021-10-19 17:50:00.152759",
+ "docstatus": 0,
+ "doctype": "Print Format Field Template",
+ "document_type": "Sales Invoice",
+ "field": "taxes",
+ "idx": 0,
+ "modified": "2021-10-19 18:13:20.894207",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Invoice Taxes",
+ "owner": "Administrator",
+ "standard": 1,
+ "template": "",
+ "template_file": "templates/print_formats/includes/taxes_and_charges.html"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 0de2a9854d6..0475231a934 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -114,8 +114,9 @@ def prepare_companywise_opening_balance(asset_data, liability_data, equity_data,
# opening_value = Aseet - liability - equity
for data in [asset_data, liability_data, equity_data]:
- account_name = get_root_account_name(data[0].root_type, company)
- opening_value += (get_opening_balance(account_name, data, company) or 0.0)
+ if data:
+ account_name = get_root_account_name(data[0].root_type, company)
+ opening_value += (get_opening_balance(account_name, data, company) or 0.0)
opening_balance[company] = opening_value
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 0094bc2eebe..31416da4ac4 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -155,6 +155,8 @@ def get_gl_entries(filters, accounting_dimensions):
if filters.get("group_by") == "Group by Voucher":
order_by_statement = "order by posting_date, voucher_type, voucher_no"
+ if filters.get("group_by") == "Group by Account":
+ order_by_statement = "order by account, posting_date, creation"
if filters.get("include_default_book_entries"):
filters['company_fb'] = frappe.db.get_value("Company",
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index fdd8d092ebb..fb23d6fc499 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -450,7 +450,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
# new row with references
new_row = journal_entry.append("accounts")
- new_row.update(jv_detail.as_dict().copy())
+
+ new_row.update((frappe.copy_doc(jv_detail)).as_dict())
new_row.set(d["dr_or_cr"], d["allocated_amount"])
new_row.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit',
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 99a6cc35dbb..cf62f496ea6 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -75,12 +75,12 @@ class Asset(AccountsController):
if self.is_existing_asset and self.purchase_invoice:
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
- def prepare_depreciation_data(self, date_of_sale=None):
+ def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None):
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
self.make_depreciation_schedule(date_of_sale)
- self.set_accumulated_depreciation(date_of_sale)
+ self.set_accumulated_depreciation(date_of_sale, date_of_return)
else:
self.finance_books = []
self.value_after_depreciation = (flt(self.gross_purchase_amount) -
@@ -182,7 +182,7 @@ class Asset(AccountsController):
d.precision("rate_of_depreciation"))
def make_depreciation_schedule(self, date_of_sale):
- if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules:
+ if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.get('schedules'):
self.schedules = []
if not self.available_for_use_date:
@@ -232,13 +232,15 @@ class Asset(AccountsController):
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
from_date, date_of_sale)
- self.append("schedules", {
- "schedule_date": date_of_sale,
- "depreciation_amount": depreciation_amount,
- "depreciation_method": d.depreciation_method,
- "finance_book": d.finance_book,
- "finance_book_id": d.idx
- })
+ if depreciation_amount > 0:
+ self.append("schedules", {
+ "schedule_date": date_of_sale,
+ "depreciation_amount": depreciation_amount,
+ "depreciation_method": d.depreciation_method,
+ "finance_book": d.finance_book,
+ "finance_book_id": d.idx
+ })
+
break
# For first row
@@ -257,11 +259,15 @@ class Asset(AccountsController):
self.to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
+ depreciation_amount_without_pro_rata = depreciation_amount
+
depreciation_amount, days, months = self.get_pro_rata_amt(d,
depreciation_amount, schedule_date, self.to_date)
- monthly_schedule_date = add_months(schedule_date, 1)
+ depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
+ depreciation_amount, d.finance_book)
+ monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
@@ -397,7 +403,28 @@ class Asset(AccountsController):
frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date")
.format(row.idx))
- def set_accumulated_depreciation(self, date_of_sale=None, ignore_booked_entry = False):
+ # to ensure that final accumulated depreciation amount is accurate
+ def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book):
+ depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
+
+ if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata:
+ depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
+
+ return depreciation_amount_for_last_row
+
+ def get_depreciation_amount_for_first_row(self, finance_book):
+ if self.has_only_one_finance_book():
+ return self.schedules[0].depreciation_amount
+ else:
+ for schedule in self.schedules:
+ if schedule.finance_book == finance_book:
+ return schedule.depreciation_amount
+
+ def has_only_one_finance_book(self):
+ if len(self.finance_books) == 1:
+ return True
+
+ def set_accumulated_depreciation(self, date_of_sale=None, date_of_return=None, ignore_booked_entry = False):
straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line']
finance_books = []
@@ -414,7 +441,7 @@ class Asset(AccountsController):
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
- if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale:
+ if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale and not date_of_return:
book = self.get('finance_books')[cint(d.finance_book_id) - 1]
depreciation_amount += flt(value_after_depreciation -
flt(book.expected_value_after_useful_life), d.precision("depreciation_amount"))
@@ -833,7 +860,7 @@ def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
- depreciation_amount = (flt(row.value_after_depreciation) -
+ depreciation_amount = (flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) -
flt(row.expected_value_after_useful_life)) / depreciation_left
# if the Depreciation Schedule is being modified after Asset Repair
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index cf4581b4a16..f162d9f39dc 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -21,12 +21,72 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
-class TestAsset(unittest.TestCase):
- def setUp(self):
+class AssetSetup(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
set_depreciation_settings_in_company()
create_asset_data()
+ enable_cwip_accounting("Computers")
+ make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location")
frappe.db.sql("delete from `tabTax Rule`")
+ @classmethod
+ def tearDownClass(cls):
+ frappe.db.rollback()
+
+class TestAsset(AssetSetup):
+ def test_asset_category_is_fetched(self):
+ """Tests if the Item's Asset Category value is assigned to the Asset, if the field is empty."""
+
+ asset = create_asset(item_code="Macbook Pro", do_not_save=1)
+ asset.asset_category = None
+ asset.save()
+
+ self.assertEqual(asset.asset_category, "Computers")
+
+ def test_gross_purchase_amount_is_mandatory(self):
+ asset = create_asset(item_code="Macbook Pro", do_not_save=1)
+ asset.gross_purchase_amount = 0
+
+ self.assertRaises(frappe.MandatoryError, asset.save)
+
+ def test_pr_or_pi_mandatory_if_not_existing_asset(self):
+ """Tests if either PI or PR is present if CWIP is enabled and is_existing_asset=0."""
+
+ asset = create_asset(item_code="Macbook Pro", do_not_save=1)
+ asset.is_existing_asset=0
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_available_for_use_date_is_after_purchase_date(self):
+ asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, do_not_save=1)
+ asset.is_existing_asset = 0
+ asset.purchase_date = getdate("2021-10-10")
+ asset.available_for_use_date = getdate("2021-10-1")
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_item_exists(self):
+ asset = create_asset(item_code="MacBook", do_not_save=1)
+
+ self.assertRaises(frappe.DoesNotExistError, asset.save)
+
+ def test_validate_item(self):
+ asset = create_asset(item_code="MacBook Pro", do_not_save=1)
+ item = frappe.get_doc("Item", "MacBook Pro")
+
+ item.disabled = 1
+ item.save()
+ self.assertRaises(frappe.ValidationError, asset.save)
+ item.disabled = 0
+
+ item.is_fixed_asset = 0
+ self.assertRaises(frappe.ValidationError, asset.save)
+ item.is_fixed_asset = 1
+
+ item.is_stock_item = 1
+ self.assertRaises(frappe.ValidationError, asset.save)
+
def test_purchase_asset(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.0, location="Test Location")
@@ -89,302 +149,16 @@ class TestAsset(unittest.TestCase):
doc.set_missing_values()
self.assertEqual(doc.items[0].is_fixed_asset, 1)
- def test_schedule_for_straight_line_method(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, 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)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2030-01-01'
- asset.purchase_date = '2030-01-01'
-
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": "2030-12-31"
- })
- asset.save()
-
- self.assertEqual(asset.status, "Draft")
- expected_schedules = [
- ["2030-12-31", 30000.00, 30000.00],
- ["2031-12-31", 30000.00, 60000.00],
- ["2032-12-31", 30000.00, 90000.00]
- ]
-
- schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
-
- self.assertEqual(schedules, expected_schedules)
-
- def test_schedule_for_straight_line_method_for_existing_asset(self):
- create_asset(is_existing_asset=1)
- asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"})
- asset.calculate_depreciation = 1
- asset.number_of_depreciations_booked = 1
- asset.opening_accumulated_depreciation = 40000
- asset.available_for_use_date = "2030-06-06"
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": "2030-12-31"
- })
- self.assertEqual(asset.status, "Draft")
- asset.save()
- expected_schedules = [
- ["2030-12-31", 14246.58, 54246.58],
- ["2031-12-31", 25000.00, 79246.58],
- ["2032-06-06", 10753.42, 90000.00]
- ]
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
-
- self.assertEqual(schedules, expected_schedules)
-
- def test_schedule_for_double_declining_method(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, 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)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2030-01-01'
- asset.purchase_date = '2030-01-01'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Double Declining Balance",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": '2030-12-31'
- })
- asset.save()
- self.assertEqual(asset.status, "Draft")
-
- expected_schedules = [
- ['2030-12-31', 66667.00, 66667.00],
- ['2031-12-31', 22222.11, 88889.11],
- ['2032-12-31', 1110.89, 90000.0]
- ]
-
- schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
-
- self.assertEqual(schedules, expected_schedules)
-
- def test_schedule_for_double_declining_method_for_existing_asset(self):
- create_asset(is_existing_asset = 1)
- asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"})
- asset.calculate_depreciation = 1
- asset.is_existing_asset = 1
- asset.number_of_depreciations_booked = 1
- asset.opening_accumulated_depreciation = 50000
- asset.available_for_use_date = '2030-01-01'
- asset.purchase_date = '2029-11-30'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Double Declining Balance",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": "2030-12-31"
- })
- asset.save()
- self.assertEqual(asset.status, "Draft")
-
- expected_schedules = [
- ["2030-12-31", 33333.50, 83333.50],
- ["2031-12-31", 6666.50, 90000.0]
- ]
-
- schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
-
- self.assertEqual(schedules, expected_schedules)
-
- def test_schedule_for_prorated_straight_line_method(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, 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)
- asset.calculate_depreciation = 1
- asset.purchase_date = '2030-01-30'
- asset.is_existing_asset = 0
- asset.available_for_use_date = "2030-01-30"
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": "2030-12-31"
- })
-
- asset.save()
-
- 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]
- ]
-
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
- for d in asset.get("schedules")]
-
- self.assertEqual(schedules, expected_schedules)
-
- def test_depreciation(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, 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)
- asset.calculate_depreciation = 1
- asset.purchase_date = '2020-01-30'
- asset.available_for_use_date = "2020-01-30"
- 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": "2020-12-31"
- })
- asset.submit()
- asset.load_from_db()
- self.assertEqual(asset.status, "Submitted")
-
- frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-")
- post_depreciation_entries(date="2021-01-01")
- asset.load_from_db()
-
- # check depreciation entry series
- self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR")
-
- expected_gle = (
- ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
- ("_Test Depreciations - _TC", 30000.0, 0.0)
- )
-
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
- where against_voucher_type='Asset' and against_voucher = %s
- order by account""", asset.name)
-
- self.assertEqual(gle, expected_gle)
- self.assertEqual(asset.get("value_after_depreciation"), 0)
-
- def test_depreciation_entry_for_wdv_without_pro_rata(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=8000.0, location="Test Location")
-
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2030-01-01'
- asset.purchase_date = '2030-01-01'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 1000,
- "depreciation_method": "Written Down Value",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": "2030-12-31"
- })
- asset.save(ignore_permissions=True)
-
- self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
-
- expected_schedules = [
- ["2030-12-31", 4000.00, 4000.00],
- ["2031-12-31", 2000.00, 6000.00],
- ["2032-12-31", 1000.00, 7000.0],
- ]
-
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
- for d in asset.get("schedules")]
-
- self.assertEqual(schedules, expected_schedules)
-
- def test_pro_rata_depreciation_entry_for_wdv(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=8000.0, location="Test Location")
-
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2030-06-06'
- asset.purchase_date = '2030-01-01'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 1000,
- "depreciation_method": "Written Down Value",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": "2030-12-31"
- })
- asset.save(ignore_permissions=True)
-
- self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
-
- expected_schedules = [
- ["2030-12-31", 2279.45, 2279.45],
- ["2031-12-31", 2860.28, 5139.73],
- ["2032-12-31", 1430.14, 6569.87],
- ["2033-06-06", 430.13, 7000.0],
- ]
-
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
- for d in asset.get("schedules")]
-
- self.assertEqual(schedules, expected_schedules)
-
- def test_depreciation_entry_cancellation(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, 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)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2020-06-06'
- asset.purchase_date = '2020-06-06'
- 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": "2020-12-31"
- })
- asset.submit()
- post_depreciation_entries(date="2021-01-01")
-
- asset.load_from_db()
-
- # cancel depreciation entry
- depr_entry = asset.get("schedules")[0].journal_entry
- self.assertTrue(depr_entry)
- frappe.get_doc("Journal Entry", depr_entry).cancel()
-
- asset.load_from_db()
- depr_entry = asset.get("schedules")[0].journal_entry
- self.assertFalse(depr_entry)
-
def test_scrap_asset(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, 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)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2020-01-01'
- asset.purchase_date = '2020-01-01'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 10,
- "frequency_of_depreciation": 1
- })
- asset.submit()
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = '2020-01-01',
+ purchase_date = '2020-01-01',
+ expected_value_after_useful_life = 10000,
+ total_number_of_depreciations = 10,
+ frequency_of_depreciation = 1,
+ submit = 1
+ )
post_depreciation_entries(date=add_months('2020-01-01', 4))
@@ -411,23 +185,18 @@ class TestAsset(unittest.TestCase):
self.assertFalse(asset.journal_entry_for_scrap)
self.assertEqual(asset.status, "Partially Depreciated")
- def test_asset_sale(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ def test_gle_made_by_asset_sale(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = '2020-06-06',
+ purchase_date = '2020-01-01',
+ expected_value_after_useful_life = 10000,
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 10,
+ depreciation_start_date = '2020-12-31',
+ submit = 1
+ )
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2020-06-06'
- asset.purchase_date = '2020-06-06'
- 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": "2020-12-31"
- })
- asset.submit()
post_depreciation_entries(date="2021-01-01")
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
@@ -455,30 +224,14 @@ class TestAsset(unittest.TestCase):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
- def test_asset_expected_value_after_useful_life(self):
+ def test_expense_head(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ qty=2, rate=200000.0, location="Test Location")
+ doc = make_invoice(pr.name)
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2020-06-06'
- asset.purchase_date = '2020-06-06'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10
- })
- asset.save()
- accumulated_depreciation_after_full_schedule = \
- max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
-
- asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) -
- flt(accumulated_depreciation_after_full_schedule))
-
- self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule)
+ self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
+ # CWIP: Capital Work In Progress
def test_cwip_accounting(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=5000, do_not_submit=True, location="Test Location")
@@ -561,14 +314,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
- def test_expense_head(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=2, rate=200000.0, location="Test Location")
-
- doc = make_invoice(pr.name)
-
- self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
-
def test_asset_cwip_toggling_cases(self):
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
@@ -637,41 +382,211 @@ class TestAsset(unittest.TestCase):
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
+class TestDepreciationMethods(AssetSetup):
+ def test_schedule_for_straight_line_method(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-01-01",
+ purchase_date = "2030-01-01",
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
+
+ self.assertEqual(asset.status, "Draft")
+ expected_schedules = [
+ ["2030-12-31", 30000.00, 30000.00],
+ ["2031-12-31", 30000.00, 60000.00],
+ ["2032-12-31", 30000.00, 90000.00]
+ ]
+
+ schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ def test_schedule_for_straight_line_method_for_existing_asset(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-06-06",
+ is_existing_asset = 1,
+ number_of_depreciations_booked = 1,
+ opening_accumulated_depreciation = 40000,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
+
+ self.assertEqual(asset.status, "Draft")
+ expected_schedules = [
+ ["2030-12-31", 14246.58, 54246.58],
+ ["2031-12-31", 25000.00, 79246.58],
+ ["2032-06-06", 10753.42, 90000.00]
+ ]
+ schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ def test_schedule_for_double_declining_method(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-01-01",
+ purchase_date = "2030-01-01",
+ depreciation_method = "Double Declining Balance",
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
+
+ self.assertEqual(asset.status, "Draft")
+
+ expected_schedules = [
+ ['2030-12-31', 66667.00, 66667.00],
+ ['2031-12-31', 22222.11, 88889.11],
+ ['2032-12-31', 1110.89, 90000.0]
+ ]
+
+ schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ def test_schedule_for_double_declining_method_for_existing_asset(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-01-01",
+ is_existing_asset = 1,
+ depreciation_method = "Double Declining Balance",
+ number_of_depreciations_booked = 1,
+ opening_accumulated_depreciation = 50000,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
+
+ self.assertEqual(asset.status, "Draft")
+
+ expected_schedules = [
+ ["2030-12-31", 33333.50, 83333.50],
+ ["2031-12-31", 6666.50, 90000.0]
+ ]
+
+ schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ def test_schedule_for_prorated_straight_line_method(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-01-30",
+ purchase_date = "2030-01-30",
+ depreciation_method = "Straight Line",
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
+
+ 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]
+ ]
+
+ schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ # WDV: Written Down Value method
+ def test_depreciation_entry_for_wdv_without_pro_rata(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-01-01",
+ purchase_date = "2030-01-01",
+ depreciation_method = "Written Down Value",
+ expected_value_after_useful_life = 12500,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
+
+ self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
+
+ expected_schedules = [
+ ["2030-12-31", 50000.0, 50000.0],
+ ["2031-12-31", 25000.0, 75000.0],
+ ["2032-12-31", 12500.0, 87500.0],
+ ]
+
+ schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ # WDV: Written Down Value method
+ def test_pro_rata_depreciation_entry_for_wdv(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-06-06",
+ purchase_date = "2030-01-01",
+ depreciation_method = "Written Down Value",
+ expected_value_after_useful_life = 12500,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
+
+ 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]
+ ]
+
+ schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
def test_discounted_wdv_depreciation_rate_for_indian_region(self):
# set indian company
company_flag = frappe.flags.company
frappe.flags.company = "_Test Company"
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=8000.0, location="Test Location")
-
- finance_book = frappe.new_doc('Finance Book')
- finance_book.finance_book_name = 'Income Tax'
+ finance_book = frappe.new_doc("Finance Book")
+ finance_book.finance_book_name = "Income Tax"
finance_book.for_income_tax = 1
- finance_book.insert(ignore_if_duplicate=1)
+ finance_book.insert(ignore_if_duplicate = True)
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
- asset.calculate_depreciation = 1
- asset.available_for_use_date = '2030-07-12'
- asset.purchase_date = '2030-01-01'
- asset.append("finance_books", {
- "finance_book": finance_book.name,
- "expected_value_after_useful_life": 1000,
- "depreciation_method": "Written Down Value",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 12,
- "depreciation_start_date": "2030-12-31"
- })
- asset.save(ignore_permissions=True)
+ asset = create_asset(
+ calculate_depreciation = 1,
+ available_for_use_date = "2030-07-12",
+ purchase_date = "2030-01-01",
+ finance_book = finance_book.name,
+ depreciation_method = "Written Down Value",
+ expected_value_after_useful_life = 12500,
+ depreciation_start_date = "2030-12-31",
+ total_number_of_depreciations = 3,
+ frequency_of_depreciation = 12
+ )
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
- ["2030-12-31", 942.47, 942.47],
- ["2031-12-31", 3528.77, 4471.24],
- ["2032-12-31", 1764.38, 6235.62],
- ["2033-07-12", 764.38, 7000.00]
+ ["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]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -682,6 +597,379 @@ class TestAsset(unittest.TestCase):
# reset indian company
frappe.flags.company = company_flag
+class TestDepreciationBasics(AssetSetup):
+ def test_depreciation_without_pro_rata(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = getdate("2019-12-31"),
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = getdate("2020-12-31"),
+ submit = 1
+ )
+
+ expected_values = [
+ ["2020-12-31", 30000, 30000],
+ ["2021-12-31", 30000, 60000],
+ ["2022-12-31", 30000, 90000]
+ ]
+
+ for i, schedule in enumerate(asset.schedules):
+ self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
+ self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
+ self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
+
+ def test_depreciation_with_pro_rata(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = getdate("2019-12-31"),
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = getdate("2020-07-01"),
+ submit = 1
+ )
+
+ expected_values = [
+ ["2020-07-01", 15000, 15000],
+ ["2021-07-01", 30000, 45000],
+ ["2022-07-01", 30000, 75000],
+ ["2022-12-31", 15000, 90000]
+ ]
+
+ for i, schedule in enumerate(asset.schedules):
+ self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
+ self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
+ self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
+
+ def test_get_depreciation_amount(self):
+ """Tests if get_depreciation_amount() returns the right value."""
+
+ from erpnext.assets.doctype.asset.asset import get_depreciation_amount
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ available_for_use_date = "2019-12-31"
+ )
+
+ asset.calculate_depreciation = 1
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31"
+ })
+
+ depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
+ self.assertEqual(depreciation_amount, 30000)
+
+ def test_make_depreciation_schedule(self):
+ """Tests if make_depreciation_schedule() returns the right values."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ depreciation_method = "Straight Line",
+ frequency_of_depreciation = 12,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2020-12-31"
+ )
+
+ expected_values = [
+ ['2020-12-31', 30000.0],
+ ['2021-12-31', 30000.0],
+ ['2022-12-31', 30000.0]
+ ]
+
+ for i, schedule in enumerate(asset.schedules):
+ self.assertEqual(expected_values[i][0], schedule.schedule_date)
+ self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
+
+ def test_set_accumulated_depreciation(self):
+ """Tests if set_accumulated_depreciation() returns the right values."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ depreciation_method = "Straight Line",
+ frequency_of_depreciation = 12,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2020-12-31"
+ )
+
+ expected_values = [30000.0, 60000.0, 90000.0]
+
+ for i, schedule in enumerate(asset.schedules):
+ self.assertEqual(expected_values[i], schedule.accumulated_depreciation_amount)
+
+ def test_check_is_pro_rata(self):
+ """Tests if check_is_pro_rata() returns the right value(i.e. checks if has_pro_rata is accurate)."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ available_for_use_date = "2019-12-31",
+ do_not_save = 1
+ )
+
+ asset.calculate_depreciation = 1
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31"
+ })
+
+ has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
+ self.assertFalse(has_pro_rata)
+
+ asset.finance_books = []
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-07-01"
+ })
+
+ has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
+ self.assertTrue(has_pro_rata)
+
+ def test_expected_value_after_useful_life_greater_than_purchase_amount(self):
+ """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000)."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 110000,
+ depreciation_start_date = "2020-07-01",
+ do_not_save = 1
+ )
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_depreciation_start_date(self):
+ """Tests if an error is raised when neither depreciation_start_date nor available_for_use_date are specified."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 110000,
+ do_not_save = 1
+ )
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_opening_accumulated_depreciation(self):
+ """Tests if an error is raised when opening_accumulated_depreciation > (gross_purchase_amount - expected_value_after_useful_life)."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2020-07-01",
+ opening_accumulated_depreciation = 100000,
+ do_not_save = 1
+ )
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_number_of_depreciations_booked(self):
+ """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2020-07-01",
+ opening_accumulated_depreciation = 10000,
+ do_not_save = 1
+ )
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_number_of_depreciations(self):
+ """Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2020-07-01",
+ opening_accumulated_depreciation = 10000,
+ number_of_depreciations_booked = 5,
+ do_not_save = 1
+ )
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_depreciation_start_date_is_before_purchase_date(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2014-07-01",
+ do_not_save = 1
+ )
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_depreciation_start_date_is_before_available_for_use_date(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ depreciation_start_date = "2018-07-01",
+ do_not_save = 1
+ )
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self):
+ asset = create_asset(item_code="Macbook Pro", do_not_save=1)
+ asset.calculate_depreciation = 1
+
+ self.assertRaises(frappe.ValidationError, asset.save)
+
+ def test_post_depreciation_entries(self):
+ """Tests if post_depreciation_entries() works as expected."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ depreciation_start_date = "2020-12-31",
+ frequency_of_depreciation = 12,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ submit = 1
+ )
+
+ post_depreciation_entries(date="2021-06-01")
+ asset.load_from_db()
+
+ self.assertTrue(asset.schedules[0].journal_entry)
+ self.assertFalse(asset.schedules[1].journal_entry)
+ self.assertFalse(asset.schedules[2].journal_entry)
+
+ def test_clear_depreciation_schedule(self):
+ """Tests if clear_depreciation_schedule() works as expected."""
+
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2019-12-31",
+ depreciation_start_date = "2020-12-31",
+ frequency_of_depreciation = 12,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ submit = 1
+ )
+
+ post_depreciation_entries(date="2021-06-01")
+ asset.load_from_db()
+
+ asset.clear_depreciation_schedule()
+
+ self.assertEqual(len(asset.schedules), 1)
+
+ def test_depreciation_entry_cancellation(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ purchase_date = "2020-06-06",
+ available_for_use_date = "2020-06-06",
+ depreciation_start_date = "2020-12-31",
+ frequency_of_depreciation = 10,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ submit = 1
+ )
+
+ post_depreciation_entries(date="2021-01-01")
+
+ asset.load_from_db()
+
+ # cancel depreciation entry
+ depr_entry = asset.get("schedules")[0].journal_entry
+ self.assertTrue(depr_entry)
+ frappe.get_doc("Journal Entry", depr_entry).cancel()
+
+ asset.load_from_db()
+ depr_entry = asset.get("schedules")[0].journal_entry
+ self.assertFalse(depr_entry)
+
+ def test_asset_expected_value_after_useful_life(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ available_for_use_date = "2020-06-06",
+ purchase_date = "2020-06-06",
+ frequency_of_depreciation = 10,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000
+ )
+
+ accumulated_depreciation_after_full_schedule = \
+ max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
+
+ asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) -
+ flt(accumulated_depreciation_after_full_schedule))
+
+ self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule)
+
+ def test_gle_made_by_depreciation_entries(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ calculate_depreciation = 1,
+ purchase_date = "2020-01-30",
+ available_for_use_date = "2020-01-30",
+ depreciation_start_date = "2020-12-31",
+ frequency_of_depreciation = 10,
+ total_number_of_depreciations = 3,
+ expected_value_after_useful_life = 10000,
+ submit = 1
+ )
+
+ self.assertEqual(asset.status, "Submitted")
+
+ frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-")
+ post_depreciation_entries(date="2021-01-01")
+ asset.load_from_db()
+
+ # check depreciation entry series
+ self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR")
+
+ expected_gle = (
+ ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
+ ("_Test Depreciations - _TC", 30000.0, 0.0)
+ )
+
+ gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ where against_voucher_type='Asset' and against_voucher = %s
+ order by account""", asset.name)
+
+ 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`
@@ -724,32 +1012,37 @@ def create_asset(**args):
asset = frappe.get_doc({
"doctype": "Asset",
"asset_name": args.asset_name or "Macbook Pro 1",
- "asset_category": "Computers",
+ "asset_category": args.asset_category or "Computers",
"item_code": args.item_code or "Macbook Pro",
- "company": args.company or"_Test Company",
- "purchase_date": "2015-01-01",
+ "company": args.company or "_Test Company",
+ "purchase_date": args.purchase_date or "2015-01-01",
"calculate_depreciation": args.calculate_depreciation or 0,
- "gross_purchase_amount": 100000,
- "purchase_receipt_amount": 100000,
- "expected_value_after_useful_life": 10000,
+ "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0,
+ "number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
+ "gross_purchase_amount": args.gross_purchase_amount or 100000,
+ "purchase_receipt_amount": args.purchase_receipt_amount or 100000,
"warehouse": args.warehouse or "_Test Warehouse - _TC",
- "available_for_use_date": "2020-06-06",
- "location": "Test Location",
- "asset_owner": "Company",
- "is_existing_asset": 1
+ "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
})
if asset.calculate_depreciation:
asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 12,
- "total_number_of_depreciations": 5
+ "finance_book": args.finance_book,
+ "depreciation_method": args.depreciation_method or "Straight Line",
+ "frequency_of_depreciation": args.frequency_of_depreciation or 12,
+ "total_number_of_depreciations": args.total_number_of_depreciations or 5,
+ "expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
+ "depreciation_start_date": args.depreciation_start_date
})
- try:
- asset.save()
- except frappe.DuplicateEntryError:
- pass
+ if not args.do_not_save:
+ try:
+ asset.save()
+ except frappe.DuplicateEntryError:
+ pass
if args.submit:
asset.submit()
@@ -800,3 +1093,6 @@ def set_depreciation_settings_in_company():
# Enable booking asset depreciation entry automatically
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
+
+def enable_cwip_accounting(asset_category, enable=1):
+ frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable)
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
index 75f42a9f783..06989a95da7 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
@@ -16,9 +16,8 @@ frappe.query_reports["Fixed Asset Register"] = {
fieldname:"status",
label: __("Status"),
fieldtype: "Select",
- options: "In Location\nDisposed",
- default: 'In Location',
- reqd: 1
+ options: "\nIn Location\nDisposed",
+ default: 'In Location'
},
{
"fieldname":"filter_based_on",
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index e370b9d0cb3..63685fef465 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -45,12 +45,13 @@ def get_conditions(filters):
if filters.get('cost_center'):
conditions["cost_center"] = filters.get('cost_center')
- # In Store assets are those that are not sold or scrapped
- operand = 'not in'
- if status not in 'In Location':
- operand = 'in'
+ if status:
+ # In Store assets are those that are not sold or scrapped
+ operand = 'not in'
+ if status not in 'In Location':
+ operand = 'in'
- conditions['status'] = (operand, ['Sold', 'Scrapped'])
+ conditions['status'] = (operand, ['Sold', 'Scrapped'])
return conditions
diff --git a/erpnext/hub_node/doctype/hub_user/__init__.py b/erpnext/buying/print_format_field_template/__init__.py
similarity index 100%
rename from erpnext/hub_node/doctype/hub_user/__init__.py
rename to erpnext/buying/print_format_field_template/__init__.py
diff --git a/erpnext/hub_node/doctype/hub_users/__init__.py b/erpnext/buying/print_format_field_template/purchase_order_taxes/__init__.py
similarity index 100%
rename from erpnext/hub_node/doctype/hub_users/__init__.py
rename to erpnext/buying/print_format_field_template/purchase_order_taxes/__init__.py
diff --git a/erpnext/buying/print_format_field_template/purchase_order_taxes/purchase_order_taxes.json b/erpnext/buying/print_format_field_template/purchase_order_taxes/purchase_order_taxes.json
new file mode 100644
index 00000000000..73b77308949
--- /dev/null
+++ b/erpnext/buying/print_format_field_template/purchase_order_taxes/purchase_order_taxes.json
@@ -0,0 +1,16 @@
+{
+ "creation": "2021-10-19 18:07:19.253457",
+ "docstatus": 0,
+ "doctype": "Print Format Field Template",
+ "document_type": "Purchase Order",
+ "field": "taxes",
+ "idx": 0,
+ "modified": "2021-10-19 18:07:19.253457",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Order Taxes",
+ "owner": "Administrator",
+ "standard": 1,
+ "template": "",
+ "template_file": "templates/print_formats/includes/taxes_and_charges.html"
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/marketplace_settings/__init__.py b/erpnext/buying/print_format_field_template/supplier_quotation_taxes/__init__.py
similarity index 100%
rename from erpnext/hub_node/doctype/marketplace_settings/__init__.py
rename to erpnext/buying/print_format_field_template/supplier_quotation_taxes/__init__.py
diff --git a/erpnext/buying/print_format_field_template/supplier_quotation_taxes/supplier_quotation_taxes.json b/erpnext/buying/print_format_field_template/supplier_quotation_taxes/supplier_quotation_taxes.json
new file mode 100644
index 00000000000..2be17a1b012
--- /dev/null
+++ b/erpnext/buying/print_format_field_template/supplier_quotation_taxes/supplier_quotation_taxes.json
@@ -0,0 +1,16 @@
+{
+ "creation": "2021-10-19 18:09:08.103919",
+ "docstatus": 0,
+ "doctype": "Print Format Field Template",
+ "document_type": "Supplier Quotation",
+ "field": "taxes",
+ "idx": 0,
+ "modified": "2021-10-19 18:09:08.103919",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Supplier Quotation Taxes",
+ "owner": "Administrator",
+ "standard": 1,
+ "template": "",
+ "template_file": "templates/print_formats/includes/taxes_and_charges.html"
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
index 1b25dd45d2d..a566d568119 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -41,10 +41,13 @@ def get_conditions(filters):
if filters.get("from_date") and filters.get("to_date"):
conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
- for field in ['company', 'name', 'status']:
+ for field in ['company', 'name']:
if filters.get(field):
conditions += f" and po.{field} = %({field})s"
+ if filters.get('status'):
+ conditions += " and po.status in %(status)s"
+
if filters.get('project'):
conditions += " and poi.project = %(project)s"
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 904f2217180..37b8f9d6d30 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -815,6 +815,38 @@ class AccountsController(TransactionBase):
if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
unlink_ref_doc_from_payment_entries(self)
+ if self.doctype == "Sales Order":
+ self.unlink_ref_doc_from_po()
+
+ def unlink_ref_doc_from_po(self):
+ so_items = []
+ for item in self.items:
+ so_items.append(item.name)
+
+ linked_po = list(set(frappe.get_all(
+ 'Purchase Order Item',
+ filters = {
+ 'sales_order': self.name,
+ 'sales_order_item': ['in', so_items],
+ 'docstatus': ['<', 2]
+ },
+ pluck='parent'
+ )))
+
+ if linked_po:
+ frappe.db.set_value(
+ 'Purchase Order Item', {
+ 'sales_order': self.name,
+ 'sales_order_item': ['in', so_items],
+ 'docstatus': ['<', 2]
+ },{
+ 'sales_order': None,
+ 'sales_order_item': None
+ }
+ )
+
+ frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
+
def get_tax_map(self):
tax_map = {}
for tax in self.get('taxes'):
@@ -1354,8 +1386,8 @@ class AccountsController(TransactionBase):
total = 0
base_total = 0
for d in self.get("payment_schedule"):
- total += flt(d.payment_amount)
- base_total += flt(d.base_payment_amount)
+ total += flt(d.payment_amount, d.precision("payment_amount"))
+ base_total += flt(d.base_payment_amount, d.precision("base_payment_amount"))
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
grand_total = self.get("rounded_total") or self.grand_total
@@ -1371,8 +1403,9 @@ class AccountsController(TransactionBase):
else:
grand_total -= self.get("total_advance")
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
- if total != flt(grand_total, self.precision("grand_total")) or \
- base_total != flt(base_grand_total, self.precision("base_grand_total")):
+
+ if flt(total, self.precision("grand_total")) != flt(grand_total, self.precision("grand_total")) or \
+ flt(base_total, self.precision("base_grand_total")) != flt(base_grand_total, self.precision("base_grand_total")):
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
def is_rounded_total_disabled(self):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 7b4566a2fa6..05ece4defee 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -132,7 +132,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select {field} from `tabSupplier`
where docstatus < 2
and ({key} like %(txt)s
- or supplier_name like %(txt)s) and disabled=0
+ or supplier_name like %(txt)s) and disabled=0
+ and (on_hold = 0 or (on_hold = 1 and CURDATE() > release_date))
{mcond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
@@ -565,7 +566,7 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
query_filters.append(['name', query_selector, dimensions])
- output = frappe.get_all(doctype, filters=query_filters)
+ output = frappe.get_list(doctype, filters=query_filters)
result = [d.name for d in output]
return [(d,) for d in set(result)]
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 70cc8a58bfe..7e7f598bc43 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -260,7 +260,9 @@ class calculate_taxes_and_totals(object):
self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
def calculate_taxes(self):
- self.doc.rounding_adjustment = 0
+ if not self.doc.get('is_consolidated'):
+ self.doc.rounding_adjustment = 0
+
# maintain actual tax rate based on idx
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
@@ -312,7 +314,9 @@ class calculate_taxes_and_totals(object):
# adjust Discount Amount loss in last tax iteration
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
- and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
+ and self.doc.discount_amount \
+ and self.doc.apply_discount_on == "Grand Total" \
+ and not self.doc.get('is_consolidated'):
self.doc.rounding_adjustment = flt(self.doc.grand_total
- flt(self.doc.discount_amount) - tax.total,
self.doc.precision("rounding_adjustment"))
@@ -405,11 +409,16 @@ class calculate_taxes_and_totals(object):
self.doc.rounding_adjustment = diff
def calculate_totals(self):
- self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \
- if self.doc.get("taxes") else flt(self.doc.net_total)
+ if self.doc.get("taxes"):
+ self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
+ else:
+ self.doc.grand_total = flt(self.doc.net_total)
- self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
+ if self.doc.get("taxes"):
+ self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
- flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
+ else:
+ self.doc.total_taxes_and_charges = 0.0
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
@@ -446,19 +455,20 @@ class calculate_taxes_and_totals(object):
self.doc.total_net_weight += d.total_weight
def set_rounded_total(self):
- if self.doc.meta.get_field("rounded_total"):
- if self.doc.is_rounded_total_disabled():
- self.doc.rounded_total = self.doc.base_rounded_total = 0
- return
+ if not self.doc.get('is_consolidated'):
+ if self.doc.meta.get_field("rounded_total"):
+ if self.doc.is_rounded_total_disabled():
+ self.doc.rounded_total = self.doc.base_rounded_total = 0
+ return
- self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
- self.doc.currency, self.doc.precision("rounded_total"))
+ self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
+ self.doc.currency, self.doc.precision("rounded_total"))
- #if print_in_rate is set, we would have already calculated rounding adjustment
- self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
- self.doc.precision("rounding_adjustment"))
+ #if print_in_rate is set, we would have already calculated rounding adjustment
+ self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
+ self.doc.precision("rounding_adjustment"))
- self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
+ self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
def _cleanup(self):
if not self.doc.get('is_consolidated'):
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 55e0efaab15..0e469ac6421 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -314,6 +314,8 @@ def make_request_for_quotation(source_name, target_doc=None):
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
def set_missing_values(source, target):
+ target.opportunity_name = source.name
+
if source.opportunity_from == "Lead":
target.lead_name = source.party_name
diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json
index 3d6fba5123a..0e872ace043 100644
--- a/erpnext/crm/doctype/prospect/prospect.json
+++ b/erpnext/crm/doctype/prospect/prospect.json
@@ -20,6 +20,7 @@
"website",
"column_break_13",
"prospect_owner",
+ "company",
"leads_section",
"prospect_lead",
"address_and_contact_section",
@@ -153,14 +154,23 @@
"fieldname": "address_and_contact_section",
"fieldtype": "Section Break",
"label": "Address and Contact"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-08-27 16:24:42.961967",
+ "migration_hash": "f39fb8f4e18a0e7fd391f0b4b52d8375",
+ "modified": "2021-11-01 13:10:36.759249",
"modified_by": "Administrator",
"module": "CRM",
"name": "Prospect",
+ "naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json
index e91c30d199a..3069042843a 100644
--- a/erpnext/demo/data/drug_list.json
+++ b/erpnext/demo/data/drug_list.json
@@ -60,7 +60,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -144,7 +143,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -226,7 +224,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -308,7 +305,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -390,7 +386,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -472,7 +467,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -554,7 +548,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -636,7 +629,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -718,7 +710,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -800,7 +791,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -882,7 +872,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -964,7 +953,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1046,7 +1034,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1128,7 +1115,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1210,7 +1196,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1292,7 +1277,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1374,7 +1358,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1456,7 +1439,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1538,7 +1520,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1620,7 +1601,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1702,7 +1682,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1784,7 +1763,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1866,7 +1844,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -1948,7 +1925,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2030,7 +2006,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2112,7 +2087,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2194,7 +2168,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2276,7 +2249,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2358,7 +2330,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2440,7 +2411,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2522,7 +2492,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2604,7 +2573,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2686,7 +2654,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2768,7 +2735,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2850,7 +2816,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -2932,7 +2897,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3014,7 +2978,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3098,7 +3061,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3180,7 +3142,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3262,7 +3223,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3344,7 +3304,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3426,7 +3385,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3508,7 +3466,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3590,7 +3547,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3672,7 +3628,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3754,7 +3709,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3836,7 +3790,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -3918,7 +3871,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4000,7 +3952,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4082,7 +4033,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4164,7 +4114,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4246,7 +4195,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4328,7 +4276,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4410,7 +4357,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4492,7 +4438,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4574,7 +4519,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4656,7 +4600,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4738,7 +4681,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4820,7 +4762,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4902,7 +4843,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -4984,7 +4924,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -5066,7 +5005,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
@@ -5148,7 +5086,6 @@
"standard_rate": 0.0,
"stock_uom": "Nos",
"supplier_items": [],
- "synced_with_hub": 0,
"taxes": [],
"thumbnail": null,
"tolerance": 0.0,
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
index 216d8f6bb3a..559bd393e62 100644
--- a/erpnext/hr/doctype/employee/employee_reminders.py
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -156,6 +156,8 @@ def get_employees_having_an_event_today(event_type):
DAY({condition_column}) = DAY(%(today)s)
AND
MONTH({condition_column}) = MONTH(%(today)s)
+ AND
+ YEAR({condition_column}) < YEAR(%(today)s)
AND
`status` = 'Active'
""",
@@ -166,6 +168,8 @@ def get_employees_having_an_event_today(event_type):
DATE_PART('day', {condition_column}) = date_part('day', %(today)s)
AND
DATE_PART('month', {condition_column}) = date_part('month', %(today)s)
+ AND
+ DATE_PART('year', {condition_column}) < date_part('year', %(today)s)
AND
"status" = 'Active'
""",
diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
index 4a1064b66b7..2f7b8fcf679 100644
--- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
+++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
@@ -100,7 +100,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-09-23 20:27:36.027728",
+ "modified": "2021-10-26 20:27:36.027728",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Taxes and Charges",
diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py
deleted file mode 100644
index 6ac3255c12a..00000000000
--- a/erpnext/hub_node/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import frappe
-
-
-@frappe.whitelist()
-def enable_hub():
- hub_settings = frappe.get_doc('Marketplace Settings')
- hub_settings.register()
- frappe.db.commit()
- return hub_settings
-
-@frappe.whitelist()
-def sync():
- hub_settings = frappe.get_doc('Marketplace Settings')
- hub_settings.sync()
diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py
deleted file mode 100644
index 55304917759..00000000000
--- a/erpnext/hub_node/api.py
+++ /dev/null
@@ -1,233 +0,0 @@
-from __future__ import unicode_literals
-
-import json
-
-import frappe
-from frappe import _
-from frappe.desk.form.load import get_attachments
-from frappe.frappeclient import FrappeClient
-from six import string_types
-
-current_user = frappe.session.user
-
-
-@frappe.whitelist()
-def register_marketplace(company, company_description):
- validate_registerer()
-
- settings = frappe.get_single('Marketplace Settings')
- message = settings.register_seller(company, company_description)
-
- if message.get('hub_seller_name'):
- settings.registered = 1
- settings.hub_seller_name = message.get('hub_seller_name')
- settings.save()
-
- settings.add_hub_user(frappe.session.user)
-
- return { 'ok': 1 }
-
-
-@frappe.whitelist()
-def register_users(user_list):
- user_list = json.loads(user_list)
-
- settings = frappe.get_single('Marketplace Settings')
-
- for user in user_list:
- settings.add_hub_user(user)
-
- return user_list
-
-
-def validate_registerer():
- if current_user == 'Administrator':
- frappe.throw(_('Please login as another user to register on Marketplace'))
-
- valid_roles = ['System Manager', 'Item Manager']
-
- if not frappe.utils.is_subset(valid_roles, frappe.get_roles()):
- frappe.throw(_('Only users with {0} role can register on Marketplace').format(', '.join(valid_roles)),
- frappe.PermissionError)
-
-
-@frappe.whitelist()
-def call_hub_method(method, params=None):
- connection = get_hub_connection()
-
- if isinstance(params, string_types):
- params = json.loads(params)
-
- params.update({
- 'cmd': 'hub.hub.api.' + method
- })
-
- response = connection.post_request(params)
- return response
-
-
-def map_fields(items):
- field_mappings = get_field_mappings()
- table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()]
-
- hub_seller_name = frappe.db.get_value('Marketplace Settings', 'Marketplace Settings', 'hub_seller_name')
-
- for item in items:
- for fieldname in table_fields:
- item.pop(fieldname, None)
-
- for mapping in field_mappings:
- local_fieldname = mapping.get('local_fieldname')
- remote_fieldname = mapping.get('remote_fieldname')
-
- value = item.get(local_fieldname)
- item.pop(local_fieldname, None)
- item[remote_fieldname] = value
-
- item['doctype'] = 'Hub Item'
- item['hub_seller'] = hub_seller_name
- item.pop('attachments', None)
-
- return items
-
-
-@frappe.whitelist()
-def get_valid_items(search_value=''):
- items = frappe.get_list(
- 'Item',
- fields=["*"],
- filters={
- 'disabled': 0,
- 'item_name': ['like', '%' + search_value + '%'],
- 'publish_in_hub': 0
- },
- order_by="modified desc"
- )
-
- valid_items = filter(lambda x: x.image and x.description, items)
-
- def prepare_item(item):
- item.source_type = "local"
- item.attachments = get_attachments('Item', item.item_code)
- return item
-
- valid_items = map(prepare_item, valid_items)
-
- return valid_items
-
-@frappe.whitelist()
-def update_item(ref_doc, data):
- data = json.loads(data)
-
- data.update(dict(doctype='Hub Item', name=ref_doc))
- try:
- connection = get_hub_connection()
- connection.update(data)
- except Exception as e:
- frappe.log_error(message=e, title='Hub Sync Error')
-
-@frappe.whitelist()
-def publish_selected_items(items_to_publish):
- items_to_publish = json.loads(items_to_publish)
- items_to_update = []
- if not len(items_to_publish):
- frappe.throw(_('No items to publish'))
-
- for item in items_to_publish:
- item_code = item.get('item_code')
- frappe.db.set_value('Item', item_code, 'publish_in_hub', 1)
-
- hub_dict = {
- 'doctype': 'Hub Tracked Item',
- 'item_code': item_code,
- 'published': 1,
- 'hub_category': item.get('hub_category'),
- 'image_list': item.get('image_list')
- }
- frappe.get_doc(hub_dict).insert(ignore_if_duplicate=True)
-
- items = map_fields(items_to_publish)
-
- try:
- item_sync_preprocess(len(items))
- convert_relative_image_urls_to_absolute(items)
-
- # TODO: Publish Progress
- connection = get_hub_connection()
- connection.insert_many(items)
-
- item_sync_postprocess()
- except Exception as e:
- frappe.log_error(message=e, title='Hub Sync Error')
-
-@frappe.whitelist()
-def unpublish_item(item_code, hub_item_name):
- ''' Remove item listing from the marketplace '''
-
- response = call_hub_method('unpublish_item', {
- 'hub_item_name': hub_item_name
- })
-
- if response:
- frappe.db.set_value('Item', item_code, 'publish_in_hub', 0)
- frappe.delete_doc('Hub Tracked Item', item_code)
- else:
- frappe.throw(_('Unable to update remote activity'))
-
-@frappe.whitelist()
-def get_unregistered_users():
- settings = frappe.get_single('Marketplace Settings')
- registered_users = [user.user for user in settings.users] + ['Administrator', 'Guest']
- all_users = [user.name for user in frappe.db.get_all('User', filters={'enabled': 1})]
- unregistered_users = [user for user in all_users if user not in registered_users]
- return unregistered_users
-
-
-def item_sync_preprocess(intended_item_publish_count):
- response = call_hub_method('pre_items_publish', {
- 'intended_item_publish_count': intended_item_publish_count
- })
-
- if response:
- frappe.db.set_value("Marketplace Settings", "Marketplace Settings", "sync_in_progress", 1)
- return response
- else:
- frappe.throw(_('Unable to update remote activity'))
-
-
-def item_sync_postprocess():
- response = call_hub_method('post_items_publish', {})
- if response:
- frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'last_sync_datetime', frappe.utils.now())
- else:
- frappe.throw(_('Unable to update remote activity'))
-
- frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'sync_in_progress', 0)
-
-
-def convert_relative_image_urls_to_absolute(items):
- from six.moves.urllib.parse import urljoin
-
- for item in items:
- file_path = item['image']
-
- if file_path.startswith('/files/'):
- item['image'] = urljoin(frappe.utils.get_url(), file_path)
-
-
-def get_hub_connection():
- settings = frappe.get_single('Marketplace Settings')
- marketplace_url = settings.marketplace_url
- hub_user = settings.get_hub_user(frappe.session.user)
-
- if hub_user:
- password = hub_user.get_password()
- hub_connection = FrappeClient(marketplace_url, hub_user.user, password)
- return hub_connection
- else:
- read_only_hub_connection = FrappeClient(marketplace_url)
- return read_only_hub_connection
-
-
-def get_field_mappings():
- return []
diff --git a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json b/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
deleted file mode 100644
index b1e421dada8..00000000000
--- a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
- "condition": "{'name': ('=', frappe.db.get_single_value('Hub Settings', 'company'))}",
- "creation": "2017-09-07 11:38:43.169065",
- "docstatus": 0,
- "doctype": "Data Migration Mapping",
- "fields": [
- {
- "is_child_table": 0,
- "local_fieldname": "name",
- "remote_fieldname": "company_name"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "country",
- "remote_fieldname": "country"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "\"city\"",
- "remote_fieldname": "seller_city"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "eval:frappe.local.site",
- "remote_fieldname": "site_name"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "eval:frappe.session.user",
- "remote_fieldname": "user"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "company_logo",
- "remote_fieldname": "company_logo"
- }
- ],
- "idx": 2,
- "local_doctype": "Company",
- "mapping_name": "Company to Hub Company",
- "mapping_type": "Push",
- "migration_id_field": "hub_sync_id",
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "name": "Company to Hub Company",
- "owner": "Administrator",
- "page_length": 10,
- "remote_objectname": "Hub Company",
- "remote_primary_key": "name"
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
deleted file mode 100644
index d11abeb4b38..00000000000
--- a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "condition": "{'reference_doctype': 'Lead', 'user': frappe.db.get_single_value('Hub Settings', 'user'), 'status': 'Pending'}",
- "creation": "2017-09-20 15:06:40.279930",
- "docstatus": 0,
- "doctype": "Data Migration Mapping",
- "fields": [
- {
- "is_child_table": 0,
- "local_fieldname": "email_id",
- "remote_fieldname": "email_id"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "lead_name",
- "remote_fieldname": "lead_name"
- }
- ],
- "idx": 0,
- "local_doctype": "Lead",
- "local_primary_key": "email_id",
- "mapping_name": "Hub Message to Lead",
- "mapping_type": "Pull",
- "migration_id_field": "hub_sync_id",
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "name": "Hub Message to Lead",
- "owner": "Administrator",
- "page_length": 10,
- "remote_objectname": "Hub Message",
- "remote_primary_key": "name"
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
deleted file mode 100644
index bcece69b38c..00000000000
--- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
- "condition": "{\"publish_in_hub\": 1}",
- "creation": "2017-09-07 13:27:52.726350",
- "docstatus": 0,
- "doctype": "Data Migration Mapping",
- "fields": [
- {
- "is_child_table": 0,
- "local_fieldname": "item_code",
- "remote_fieldname": "item_code"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "item_name",
- "remote_fieldname": "item_name"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')",
- "remote_fieldname": "hub_seller"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "image",
- "remote_fieldname": "image"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "image_list",
- "remote_fieldname": "image_list"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "item_group",
- "remote_fieldname": "item_group"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "hub_category",
- "remote_fieldname": "hub_category"
- }
- ],
- "idx": 1,
- "local_doctype": "Item",
- "mapping_name": "Item to Hub Item",
- "mapping_type": "Push",
- "migration_id_field": "hub_sync_id",
- "modified": "2018-08-19 22:20:25.727581",
- "modified_by": "Administrator",
- "name": "Item to Hub Item",
- "owner": "Administrator",
- "page_length": 10,
- "remote_objectname": "Hub Item",
- "remote_primary_key": "item_code"
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
deleted file mode 100644
index e90b1dd1e8d..00000000000
--- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "creation": "2017-09-07 11:39:38.445902",
- "docstatus": 0,
- "doctype": "Data Migration Plan",
- "idx": 1,
- "mappings": [
- {
- "enabled": 1,
- "mapping": "Item to Hub Item"
- }
- ],
- "modified": "2018-08-19 22:20:25.644602",
- "modified_by": "Administrator",
- "module": "Hub Node",
- "name": "Hub Sync",
- "owner": "Administrator",
- "plan_name": "Hub Sync",
- "postprocess_method": "erpnext.hub_node.api.item_sync_postprocess"
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js
deleted file mode 100644
index 660532d13df..00000000000
--- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hub Tracked Item', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
deleted file mode 100644
index 7d07ba40938..00000000000
--- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
+++ /dev/null
@@ -1,210 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:item_code",
- "beta": 0,
- "creation": "2018-03-18 09:33:50.267762",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 1
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hub_category",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Hub Category",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "published",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Published",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "image_list",
- "fieldtype": "Long Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Image List",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-12-10 11:37:35.951019",
- "modified_by": "Administrator",
- "module": "Hub Node",
- "name": "Hub Tracked Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Item Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 1,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py
deleted file mode 100644
index 823c79eb72b..00000000000
--- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-from frappe.model.document import Document
-
-
-class HubTrackedItem(Document):
- pass
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py
deleted file mode 100644
index c403f902a2c..00000000000
--- a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import unittest
-
-
-class TestHubTrackedItem(unittest.TestCase):
- pass
diff --git a/erpnext/hub_node/doctype/hub_user/hub_user.json b/erpnext/hub_node/doctype/hub_user/hub_user.json
deleted file mode 100644
index f51ffb4387d..00000000000
--- a/erpnext/hub_node/doctype/hub_user/hub_user.json
+++ /dev/null
@@ -1,140 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2018-08-31 12:36:45.627531",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "user",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 1
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hub_user_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Hub User",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "password",
- "fieldtype": "Password",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Hub Password",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Hub Node",
- "name": "Hub User",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_user/hub_user.py b/erpnext/hub_node/doctype/hub_user/hub_user.py
deleted file mode 100644
index 1f7c8fc3f20..00000000000
--- a/erpnext/hub_node/doctype/hub_user/hub_user.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-from frappe.model.document import Document
-
-
-class HubUser(Document):
- pass
diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.json b/erpnext/hub_node/doctype/hub_users/hub_users.json
deleted file mode 100644
index d42f3fdf1b7..00000000000
--- a/erpnext/hub_node/doctype/hub_users/hub_users.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-03-06 04:38:49.891787",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "user",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Hub Node",
- "name": "Hub Users",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.py b/erpnext/hub_node/doctype/hub_users/hub_users.py
deleted file mode 100644
index e08ed68ed8f..00000000000
--- a/erpnext/hub_node/doctype/hub_users/hub_users.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-from frappe.model.document import Document
-
-
-class HubUsers(Document):
- pass
diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js
deleted file mode 100644
index 36da832c7c0..00000000000
--- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Marketplace Settings', {
- refresh: function(frm) {
- $('#toolbar-user .marketplace-link').toggle(!frm.doc.disable_marketplace);
- },
-});
diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json
deleted file mode 100644
index e784f68fcf3..00000000000
--- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json
+++ /dev/null
@@ -1,410 +0,0 @@
-{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2018-08-31 15:54:38.795263",
- "custom": 0,
- "description": "",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "disable_marketplace",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Disable Marketplace",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!doc.disable_marketplace",
- "fieldname": "marketplace_settings_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Marketplace Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "https://hubmarket.org",
- "fieldname": "marketplace_url",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Marketplace URL (to hide and update label)",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "registered",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Registered",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sync_in_progress",
- "fieldtype": "Check",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sync in Progress",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hub_seller_name",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Hub Seller Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "users",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Users",
- "length": 0,
- "no_copy": 0,
- "options": "Hub User",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "last_sync_datetime",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Last Sync On",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "depends_on": "eval:1",
- "fieldname": "custom_data",
- "fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Custom Data",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Hub Node",
- "name": "Marketplace Settings",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "All",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 0
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py
deleted file mode 100644
index 33d23f6eae4..00000000000
--- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import json
-
-import frappe
-from frappe.frappeclient import FrappeClient
-from frappe.model.document import Document
-from frappe.utils import cint
-
-
-class MarketplaceSettings(Document):
-
- def register_seller(self, company, company_description):
-
- country, currency, company_logo = frappe.db.get_value('Company', company,
- ['country', 'default_currency', 'company_logo'])
-
- company_details = {
- 'company': company,
- 'country': country,
- 'currency': currency,
- 'company_description': company_description,
- 'company_logo': company_logo,
- 'site_name': frappe.utils.get_url()
- }
-
- hub_connection = self.get_connection()
-
- response = hub_connection.post_request({
- 'cmd': 'hub.hub.api.add_hub_seller',
- 'company_details': json.dumps(company_details)
- })
-
- return response
-
-
- def add_hub_user(self, user_email):
- '''Create a Hub User and User record on hub server
- and if successfull append it to Hub User table
- '''
-
- if not self.registered:
- return
-
- hub_connection = self.get_connection()
-
- first_name, last_name = frappe.db.get_value('User', user_email, ['first_name', 'last_name'])
-
- hub_user = hub_connection.post_request({
- 'cmd': 'hub.hub.api.add_hub_user',
- 'user_email': user_email,
- 'first_name': first_name,
- 'last_name': last_name,
- 'hub_seller': self.hub_seller_name
- })
-
- self.append('users', {
- 'user': hub_user.get('user_email'),
- 'hub_user_name': hub_user.get('hub_user_name'),
- 'password': hub_user.get('password')
- })
-
- self.save()
-
- def get_hub_user(self, user):
- '''Return the Hub User doc from the `users` table if password is set'''
-
- filtered_users = list(filter(
- lambda x: x.user == user and x.password,
- self.users
- ))
-
- if filtered_users:
- return filtered_users[0]
-
-
- def get_connection(self):
- return FrappeClient(self.marketplace_url)
-
-
- def unregister(self):
- """Disable the User on hubmarket.org"""
-
-@frappe.whitelist()
-def is_marketplace_enabled():
- if not hasattr(frappe.local, 'is_marketplace_enabled'):
- frappe.local.is_marketplace_enabled = cint(frappe.db.get_single_value('Marketplace Settings',
- 'disable_marketplace'))
-
- return frappe.local.is_marketplace_enabled
diff --git a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py b/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py
deleted file mode 100644
index 7922f45ab59..00000000000
--- a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import unittest
-
-
-class TestMarketplaceSettings(unittest.TestCase):
- pass
diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py
deleted file mode 100644
index 2e4c2668436..00000000000
--- a/erpnext/hub_node/legacy.py
+++ /dev/null
@@ -1,148 +0,0 @@
-from __future__ import unicode_literals
-
-import json
-
-import frappe
-from frappe.contacts.doctype.contact.contact import get_default_contact
-from frappe.frappeclient import FrappeClient
-from frappe.utils import nowdate
-from frappe.utils.nestedset import get_root_of
-
-
-def get_list(doctype, start, limit, fields, filters, order_by):
- pass
-
-def get_hub_connection():
- if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
- hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
- hub_connection = hub_connector.get_connection()
- return hub_connection.connection
-
- # read-only connection
- hub_connection = FrappeClient(frappe.conf.hub_url)
- return hub_connection
-
-def make_opportunity(buyer_name, email_id):
- buyer_name = "HUB-" + buyer_name
-
- if not frappe.db.exists('Lead', {'email_id': email_id}):
- lead = frappe.new_doc("Lead")
- lead.lead_name = buyer_name
- lead.email_id = email_id
- lead.save(ignore_permissions=True)
-
- o = frappe.new_doc("Opportunity")
- o.opportunity_from = "Lead"
- o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"]
- o.save(ignore_permissions=True)
-
-@frappe.whitelist()
-def make_rfq_and_send_opportunity(item, supplier):
- supplier = make_supplier(supplier)
- contact = make_contact(supplier)
- item = make_item(item)
- rfq = make_rfq(item, supplier, contact)
- status = send_opportunity(contact)
-
- return {
- 'rfq': rfq,
- 'hub_document_created': status
- }
-
-def make_supplier(supplier):
- # make supplier if not already exists
- supplier = frappe._dict(json.loads(supplier))
-
- if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}):
- supplier_doc = frappe.get_doc({
- 'doctype': 'Supplier',
- 'supplier_name': supplier.supplier_name,
- 'supplier_group': supplier.supplier_group,
- 'supplier_email': supplier.supplier_email
- }).insert()
- else:
- supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name)
-
- return supplier_doc
-
-def make_contact(supplier):
- contact_name = get_default_contact('Supplier', supplier.supplier_name)
- # make contact if not already exists
- if not contact_name:
- contact = frappe.get_doc({
- 'doctype': 'Contact',
- 'first_name': supplier.supplier_name,
- 'is_primary_contact': 1,
- 'links': [
- {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
- ]
- })
- contact.add_email(supplier.supplier_email, is_primary=True)
- contact.insert()
- else:
- contact = frappe.get_doc('Contact', contact_name)
-
- return contact
-
-def make_item(item):
- # make item if not already exists
- item = frappe._dict(json.loads(item))
-
- if not frappe.db.exists('Item', {'item_code': item.item_code}):
- item_doc = frappe.get_doc({
- 'doctype': 'Item',
- 'item_code': item.item_code,
- 'item_group': item.item_group,
- 'is_item_from_hub': 1
- }).insert()
- else:
- item_doc = frappe.get_doc('Item', item.item_code)
-
- return item_doc
-
-def make_rfq(item, supplier, contact):
- # make rfq
- rfq = frappe.get_doc({
- 'doctype': 'Request for Quotation',
- 'transaction_date': nowdate(),
- 'status': 'Draft',
- 'company': frappe.db.get_single_value('Marketplace Settings', 'company'),
- 'message_for_supplier': 'Please supply the specified items at the best possible rates',
- 'suppliers': [
- { 'supplier': supplier.name, 'contact': contact.name }
- ],
- 'items': [
- {
- 'item_code': item.item_code,
- 'qty': 1,
- 'schedule_date': nowdate(),
- 'warehouse': item.default_warehouse or get_root_of("Warehouse"),
- 'description': item.description,
- 'uom': item.stock_uom
- }
- ]
- }).insert()
-
- rfq.save()
- rfq.submit()
- return rfq
-
-def send_opportunity(contact):
- # Make Hub Message on Hub with lead data
- doc = {
- 'doctype': 'Lead',
- 'lead_name': frappe.db.get_single_value('Marketplace Settings', 'company'),
- 'email_id': frappe.db.get_single_value('Marketplace Settings', 'user')
- }
-
- args = frappe._dict(dict(
- doctype='Hub Message',
- reference_doctype='Lead',
- data=json.dumps(doc),
- user=contact.email_id
- ))
-
- connection = get_hub_connection()
- response = connection.insert('Hub Message', args)
-
- return response.ok
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 7e539183b0c..62187077f3d 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -436,7 +436,7 @@
"description": "Item Image (if not slideshow)",
"fieldname": "website_image",
"fieldtype": "Attach Image",
- "label": "Image"
+ "label": "Website Image"
},
{
"allow_on_submit": 1,
@@ -539,7 +539,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2021-05-16 12:25:09.081968",
+ "modified": "2021-10-27 14:52:04.500251",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 232e3a0b0ff..2cd8f8c15af 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -307,6 +307,9 @@ class BOM(WebsiteGenerator):
existing_bom_cost = self.total_cost
for d in self.get("items"):
+ if not d.item_code:
+ continue
+
rate = self.get_rm_rate({
"company": self.company,
"item_code": d.item_code,
@@ -599,7 +602,7 @@ class BOM(WebsiteGenerator):
for d in self.get('items'):
if d.bom_no:
self.get_child_exploded_items(d.bom_no, d.stock_qty)
- else:
+ elif d.item_code:
self.add_to_cur_exploded_items(frappe._dict({
'item_code' : d.item_code,
'item_name' : d.item_name,
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index e1d79be81c4..b3b94071f0b 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -605,7 +605,8 @@ def make_material_request(source_name, target_doc=None):
"doctype": "Material Request Item",
"field_map": {
"required_qty": "qty",
- "uom": "stock_uom"
+ "uom": "stock_uom",
+ "name": "job_card_item"
},
"postprocess": update_item,
}
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 7e6fc3c4a64..2424ef9a71c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -311,7 +311,7 @@ class ProductionPlan(Document):
if self.total_produced_qty > 0:
self.status = "In Process"
- if self.total_produced_qty >= self.total_planned_qty:
+ if self.check_have_work_orders_completed():
self.status = "Completed"
if self.status != 'Completed':
@@ -575,6 +575,15 @@ class ProductionPlan(Document):
self.append("sub_assembly_items", data)
+ def check_have_work_orders_completed(self):
+ wo_status = frappe.db.get_list(
+ "Work Order",
+ filters={"production_plan": self.name},
+ fields="status",
+ pluck="status"
+ )
+ return all(s == "Completed" for s in wo_status)
+
@frappe.whitelist()
def download_raw_materials(doc, warehouses=None):
if isinstance(doc, str):
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 913fc85af61..7f8e816a22a 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -182,6 +182,7 @@
"reqd": 1
},
{
+ "default": "1.0",
"fieldname": "qty",
"fieldtype": "Float",
"label": "Qty To Manufacture",
@@ -572,10 +573,11 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2021-08-24 15:14:03.844937",
+ "modified": "2021-10-27 19:21:35.139888",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
+ "naming_rule": "By \"Naming Series\" field",
"nsm_parent_field": "parent_work_order",
"owner": "Administrator",
"permissions": [
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index e282dd3ecba..f881e1bf16a 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -685,9 +685,7 @@ class WorkOrder(Document):
if not d.operation:
d.operation = operation
else:
- # Attribute a big number (999) to idx for sorting putpose in case idx is NULL
- # For instance in BOM Explosion Item child table, the items coming from sub assembly items
- for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
+ for item in sorted(item_dict.values(), key=lambda d: d['idx'] or float('inf')):
self.append('required_items', {
'rate': item.rate,
'amount': item.rate * item.qty,
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index a9f94ce1335..15a24a746f7 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -20,9 +20,8 @@ Agriculture
ERPNext Integrations
Non Profit
Hotels
-Hub Node
Quality Management
Communication
Loan Management
Payroll
-Telephony
+Telephony
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 20e54e08e6e..2c21ab6c3ae 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -58,11 +58,7 @@ erpnext.patches.v11_0.set_department_for_doctypes
erpnext.patches.v11_0.update_allow_transfer_for_manufacture
erpnext.patches.v11_0.add_item_group_defaults
erpnext.patches.v11_0.add_expense_claim_default_account
-execute:frappe.delete_doc("Page", "hub")
-erpnext.patches.v11_0.reset_publish_in_hub_for_all_items
-erpnext.patches.v11_0.update_hub_url # 2018-08-31 # 2018-09-03
erpnext.patches.v11_0.make_job_card
-erpnext.patches.v10_0.delete_hub_documents # 12-08-2018
erpnext.patches.v11_0.add_default_dispatch_notification_template
erpnext.patches.v11_0.add_market_segments
erpnext.patches.v11_0.add_sales_stages
@@ -153,7 +149,6 @@ erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim
erpnext.patches.v12_0.add_eway_bill_in_delivery_note
erpnext.patches.v12_0.set_lead_title_field
erpnext.patches.v12_0.set_permission_einvoicing
-erpnext.patches.v12_0.set_published_in_hub_tracked_item
erpnext.patches.v12_0.set_job_offer_applicant_email
erpnext.patches.v12_0.create_irs_1099_field_united_states
erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
@@ -308,6 +303,9 @@ erpnext.patches.v13_0.set_status_in_maintenance_schedule_table
erpnext.patches.v13_0.add_default_interview_notification_templates
erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting
erpnext.patches.v13_0.requeue_failed_reposts
+erpnext.patches.v12_0.update_production_plan_status
erpnext.patches.v13_0.healthcare_deprecation_warning
erpnext.patches.v14_0.delete_healthcare_doctypes
+erpnext.patches.v13_0.update_category_in_ltds_certificate
erpnext.patches.v13_0.create_pan_field_for_india #2
+erpnext.patches.v14_0.delete_hub_doctypes
diff --git a/erpnext/patches/v10_0/delete_hub_documents.py b/erpnext/patches/v10_0/delete_hub_documents.py
deleted file mode 100644
index 16c7abfc978..00000000000
--- a/erpnext/patches/v10_0/delete_hub_documents.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from __future__ import unicode_literals
-
-import frappe
-
-
-def execute():
- for dt, dn in (("Page", "Hub"), ("DocType", "Hub Settings"), ("DocType", "Hub Category")):
- frappe.delete_doc(dt, dn, ignore_missing=True)
-
- if frappe.db.exists("DocType", "Data Migration Plan"):
- data_migration_plans = frappe.get_all("Data Migration Plan", filters={"module": 'Hub Node'})
- for plan in data_migration_plans:
- plan_doc = frappe.get_doc("Data Migration Plan", plan.name)
- for m in plan_doc.get("mappings"):
- frappe.delete_doc("Data Migration Mapping", m.mapping, force=True)
- docs = frappe.get_all("Data Migration Run", filters={"data_migration_plan": plan.name})
- for doc in docs:
- frappe.delete_doc("Data Migration Run", doc.name)
- frappe.delete_doc("Data Migration Plan", plan.name)
diff --git a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py
deleted file mode 100644
index a664baf6dd6..00000000000
--- a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from __future__ import unicode_literals
-
-import frappe
-
-
-def execute():
- frappe.reload_doc('stock', 'doctype', 'item')
- frappe.db.sql("""update `tabItem` set publish_in_hub = 0""")
diff --git a/erpnext/patches/v11_0/update_hub_url.py b/erpnext/patches/v11_0/update_hub_url.py
deleted file mode 100644
index c89b9b50607..00000000000
--- a/erpnext/patches/v11_0/update_hub_url.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from __future__ import unicode_literals
-
-import frappe
-
-
-def execute():
- frappe.reload_doc('hub_node', 'doctype', 'Marketplace Settings')
- frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'marketplace_url', 'https://hubmarket.org')
diff --git a/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py b/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py
deleted file mode 100644
index 73c6ce8220e..00000000000
--- a/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from __future__ import unicode_literals
-
-import frappe
-
-
-def execute():
- frappe.reload_doc("Hub Node", "doctype", "Hub Tracked Item")
- if not frappe.db.a_row_exists("Hub Tracked Item"):
- return
-
- frappe.db.sql('''
- Update `tabHub Tracked Item`
- SET published = 1
- ''')
diff --git a/erpnext/patches/v12_0/update_production_plan_status.py b/erpnext/patches/v12_0/update_production_plan_status.py
new file mode 100644
index 00000000000..06fc503a33f
--- /dev/null
+++ b/erpnext/patches/v12_0/update_production_plan_status.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2021, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+
+
+def execute():
+ frappe.reload_doc("manufacturing", "doctype", "production_plan")
+ frappe.db.sql("""
+ UPDATE `tabProduction Plan` ppl
+ SET status = "Completed"
+ WHERE ppl.name IN (
+ SELECT ss.name FROM (
+ SELECT
+ (
+ count(wo.status = "Completed") =
+ count(pp.name)
+ ) =
+ (
+ pp.status != "Completed"
+ AND pp.total_produced_qty >= pp.total_planned_qty
+ ) AS should_set,
+ pp.name AS name
+ FROM
+ `tabWork Order` wo INNER JOIN`tabProduction Plan` pp
+ ON wo.production_plan = pp.name
+ GROUP BY pp.name
+ HAVING should_set = 1
+ ) ss
+ )
+ """)
diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py
new file mode 100644
index 00000000000..a5f5a23449a
--- /dev/null
+++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py
@@ -0,0 +1,20 @@
+import frappe
+
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ frappe.reload_doc('regional', 'doctype', 'lower_deduction_certificate')
+
+ ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc")
+ supplier = frappe.qb.DocType("Supplier")
+
+ frappe.qb.update(ldc).inner_join(supplier).on(
+ ldc.supplier == supplier.name
+ ).set(
+ ldc.tax_withholding_category, supplier.tax_withholding_category
+ ).where(
+ ldc.tax_withholding_category.isnull()
+ ).run()
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/delete_hub_doctypes.py b/erpnext/patches/v14_0/delete_hub_doctypes.py
new file mode 100644
index 00000000000..d1e9e31f0c2
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_hub_doctypes.py
@@ -0,0 +1,10 @@
+import frappe
+
+
+def execute():
+
+ doctypes = frappe.get_all("DocType", {"module": "Hub Node", "custom": 0}, pluck='name')
+ for doctype in doctypes:
+ frappe.delete_doc("DocType", doctype, ignore_missing=True)
+
+ frappe.delete_doc("Module Def", "Hub Node", ignore_missing=True, force=True)
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 6b70dab8037..f8e817770d5 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -1,14 +1,10 @@
{
"css/erpnext.css": [
"public/less/erpnext.less",
- "public/less/hub.less",
"public/scss/call_popup.scss",
"public/scss/point-of-sale.scss",
"public/scss/hierarchy_chart.scss"
],
- "css/marketplace.css": [
- "public/less/hub.less"
- ],
"js/erpnext-web.min.js": [
"public/js/website_utils.js",
"public/js/shopping_cart.js"
@@ -17,9 +13,6 @@
"public/scss/website.scss",
"public/scss/shopping_cart.scss"
],
- "js/marketplace.min.js": [
- "public/js/hub/marketplace.js"
- ],
"js/erpnext.min.js": [
"public/js/conf.js",
"public/js/utils.js",
@@ -41,7 +34,6 @@
"public/js/utils/supplier_quick_entry.js",
"public/js/education/student_button.html",
"public/js/education/assessment_result_tool.html",
- "public/js/hub/hub_factory.js",
"public/js/call_popup/call_popup.js",
"public/js/utils/dimension_tree_filter.js",
"public/js/telephony.js",
diff --git a/erpnext/public/images/hub_logo.svg b/erpnext/public/images/hub_logo.svg
deleted file mode 100644
index 4af482176ea..00000000000
--- a/erpnext/public/images/hub_logo.svg
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
-
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index febdb24da34..5259bdcc765 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -18,7 +18,6 @@ import "./utils/customer_quick_entry";
import "./utils/supplier_quick_entry";
import "./education/student_button.html";
import "./education/assessment_result_tool.html";
-import "./hub/hub_factory";
import "./call_popup/call_popup";
import "./utils/dimension_tree_filter";
import "./telephony";
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 0d79b10c041..1a309ba0156 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -113,15 +113,15 @@ function get_filters() {
"fieldname":"period_start_date",
"label": __("Start Date"),
"fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
+ "reqd": 1,
+ "depends_on": "eval:doc.filter_based_on == 'Date Range'"
},
{
"fieldname":"period_end_date",
"label": __("End Date"),
"fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
+ "reqd": 1,
+ "depends_on": "eval:doc.filter_based_on == 'Date Range'"
},
{
"fieldname":"from_fiscal_year",
@@ -129,7 +129,8 @@ function get_filters() {
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
+ "reqd": 1,
+ "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
},
{
"fieldname":"to_fiscal_year",
@@ -137,7 +138,8 @@ function get_filters() {
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
+ "reqd": 1,
+ "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
},
{
"fieldname": "periodicity",
diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue
deleted file mode 100644
index 54c359766d3..00000000000
--- a/erpnext/public/js/hub/PageContainer.vue
+++ /dev/null
@@ -1,119 +0,0 @@
-
- Details Ratings Desc Desc
-
-
- Name
-
-
- {{ section.title }}
-
-
-
- ${rating_html} -
-- ${data.content} -
-- {{ __('You can Feature upto 8 items.') }} -
- -{{ 'See All' }}
-{{ valid_items_instruction }}
- -- {{ __('You can publish upto 200 items.') }} -
-