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 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - 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 @@ - - - diff --git a/erpnext/public/js/hub/Sidebar.vue b/erpnext/public/js/hub/Sidebar.vue deleted file mode 100644 index 66c291ec52f..00000000000 --- a/erpnext/public/js/hub/Sidebar.vue +++ /dev/null @@ -1,110 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/CommentInput.vue b/erpnext/public/js/hub/components/CommentInput.vue deleted file mode 100644 index 31562c7a284..00000000000 --- a/erpnext/public/js/hub/components/CommentInput.vue +++ /dev/null @@ -1,39 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/DetailHeaderItem.vue b/erpnext/public/js/hub/components/DetailHeaderItem.vue deleted file mode 100644 index a6c5f066f28..00000000000 --- a/erpnext/public/js/hub/components/DetailHeaderItem.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue deleted file mode 100644 index 942c1ebdb34..00000000000 --- a/erpnext/public/js/hub/components/DetailView.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/EmptyState.vue b/erpnext/public/js/hub/components/EmptyState.vue deleted file mode 100644 index e3a33a08306..00000000000 --- a/erpnext/public/js/hub/components/EmptyState.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/Image.vue b/erpnext/public/js/hub/components/Image.vue deleted file mode 100644 index 9acf4210322..00000000000 --- a/erpnext/public/js/hub/components/Image.vue +++ /dev/null @@ -1,40 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue deleted file mode 100644 index 675ad86645b..00000000000 --- a/erpnext/public/js/hub/components/ItemCard.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/ItemCardsContainer.vue b/erpnext/public/js/hub/components/ItemCardsContainer.vue deleted file mode 100644 index 0a20bcdc636..00000000000 --- a/erpnext/public/js/hub/components/ItemCardsContainer.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/ItemListCard.vue b/erpnext/public/js/hub/components/ItemListCard.vue deleted file mode 100644 index 7f6fb77d76e..00000000000 --- a/erpnext/public/js/hub/components/ItemListCard.vue +++ /dev/null @@ -1,21 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/NotificationMessage.vue b/erpnext/public/js/hub/components/NotificationMessage.vue deleted file mode 100644 index c26672635c3..00000000000 --- a/erpnext/public/js/hub/components/NotificationMessage.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/Rating.vue b/erpnext/public/js/hub/components/Rating.vue deleted file mode 100644 index 33290b8e202..00000000000 --- a/erpnext/public/js/hub/components/Rating.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/ReviewArea.vue b/erpnext/public/js/hub/components/ReviewArea.vue deleted file mode 100644 index aa83bb0e465..00000000000 --- a/erpnext/public/js/hub/components/ReviewArea.vue +++ /dev/null @@ -1,140 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/ReviewTimelineItem.vue b/erpnext/public/js/hub/components/ReviewTimelineItem.vue deleted file mode 100644 index d0e83f3b1cd..00000000000 --- a/erpnext/public/js/hub/components/ReviewTimelineItem.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/SearchInput.vue b/erpnext/public/js/hub/components/SearchInput.vue deleted file mode 100644 index 4b1ce6e4ef9..00000000000 --- a/erpnext/public/js/hub/components/SearchInput.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/SectionHeader.vue b/erpnext/public/js/hub/components/SectionHeader.vue deleted file mode 100644 index 05a2f838a0a..00000000000 --- a/erpnext/public/js/hub/components/SectionHeader.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/erpnext/public/js/hub/components/TimelineItem.vue b/erpnext/public/js/hub/components/TimelineItem.vue deleted file mode 100644 index d13c842beb8..00000000000 --- a/erpnext/public/js/hub/components/TimelineItem.vue +++ /dev/null @@ -1,9 +0,0 @@ -/* Saving this for later */ - diff --git a/erpnext/public/js/hub/components/edit_details_dialog.js b/erpnext/public/js/hub/components/edit_details_dialog.js deleted file mode 100644 index 97c5f83a13a..00000000000 --- a/erpnext/public/js/hub/components/edit_details_dialog.js +++ /dev/null @@ -1,41 +0,0 @@ -function edit_details_dialog(params) { - let dialog = new frappe.ui.Dialog({ - title: __('Update Details'), - fields: [ - { - label: 'Item Name', - fieldname: 'item_name', - fieldtype: 'Data', - default: params.defaults.item_name, - reqd: 1 - }, - { - label: 'Hub Category', - fieldname: 'hub_category', - fieldtype: 'Autocomplete', - default: params.defaults.hub_category, - options: [], - reqd: 1 - }, - { - label: 'Description', - fieldname: 'description', - fieldtype: 'Text', - default: params.defaults.description, - options: [], - reqd: 1 - } - ], - primary_action_label: params.primary_action.label || __('Update Details'), - primary_action: params.primary_action.fn - }); - - hub.call('get_categories').then(categories => { - categories = categories.map(d => d.name); - dialog.fields_dict.hub_category.set_data(categories); - }); - - return dialog; -} - -export { edit_details_dialog }; diff --git a/erpnext/public/js/hub/components/item_publish_dialog.js b/erpnext/public/js/hub/components/item_publish_dialog.js deleted file mode 100644 index 08de5b30b6c..00000000000 --- a/erpnext/public/js/hub/components/item_publish_dialog.js +++ /dev/null @@ -1,39 +0,0 @@ -function ItemPublishDialog(primary_action, secondary_action) { - let dialog = new frappe.ui.Dialog({ - title: __('Edit Publishing Details'), - fields: [ - { - label: __('Item Code'), - fieldname: 'item_code', - fieldtype: 'Data', - read_only: 1 - }, - { - label: __('Hub Category'), - fieldname: 'hub_category', - fieldtype: 'Autocomplete', - options: [], - reqd: 1 - }, - { - label: __('Images'), - fieldname: 'image_list', - fieldtype: 'MultiSelect', - options: [], - reqd: 1 - } - ], - primary_action_label: primary_action.label || __('Set Details'), - primary_action: primary_action.fn, - secondary_action: secondary_action.fn - }); - - hub.call('get_categories').then(categories => { - categories = categories.map(d => d.name); - dialog.fields_dict.hub_category.set_data(categories); - }); - - return dialog; -} - -export { ItemPublishDialog }; diff --git a/erpnext/public/js/hub/components/profile_dialog.js b/erpnext/public/js/hub/components/profile_dialog.js deleted file mode 100644 index 8e3abc37eb8..00000000000 --- a/erpnext/public/js/hub/components/profile_dialog.js +++ /dev/null @@ -1,56 +0,0 @@ -const ProfileDialog = (title = __('Edit Profile'), action={}) => { - const fields = [ - { - fieldtype: 'Link', - fieldname: 'company', - label: __('Company'), - options: 'Company' - }, - { - fieldtype: 'Read Only', - fieldname: 'email', - label: __('Email') - }, - { - label: __('About your company'), - fieldname: 'company_description', - fieldtype: 'Text' - } - ]; - - let dialog = new frappe.ui.Dialog({ - title: title, - fields: fields, - primary_action_label: action.label || __('Update'), - primary_action: () => { - const form_values = dialog.get_values(); - let values_filled = true; - - // TODO: Say "we notice that the company description and logo isn't set. Please set them in master." - // Only then allow to register - - const mandatory_fields = ['company', 'company_description']; - mandatory_fields.forEach(field => { - const value = form_values[field]; - if (!value) { - dialog.set_df_property(field, 'reqd', 1); - values_filled = false; - } - }); - if (!values_filled) return; - - action.on_submit(form_values); - } - }); - - // Post create - const default_company = frappe.defaults.get_default('company'); - dialog.set_value('company', default_company); - dialog.set_value('email', frappe.session.user); - - return dialog; -} - -export { - ProfileDialog -} diff --git a/erpnext/public/js/hub/components/reviews.js b/erpnext/public/js/hub/components/reviews.js deleted file mode 100644 index e34d68038f7..00000000000 --- a/erpnext/public/js/hub/components/reviews.js +++ /dev/null @@ -1,80 +0,0 @@ -function get_review_html(review) { - let username = review.username || review.user || __("Anonymous"); - - let image_html = review.user_image - ? `
` - : `
${frappe.get_abbr(username)}
` - - let edit_html = review.own - ? ` -
- - ${'data.edit'} - -
` - : ''; - - let rating_html = get_rating_html(review.rating); - - return get_timeline_item(review, image_html, edit_html, rating_html); -} - -function get_timeline_item(data, image_html, edit_html, rating_html) { - return `
- -
-
-
${edit_html}
- -
- - ${image_html} - - -
- - - ${data.username} - - - - - -
-
-
-
-

- ${rating_html} -

-
${data.subject}
-

- ${data.content} -

-
-
-
-
-
`; -} - -function get_rating_html(rating) { - let rating_html = ``; - for (var i = 0; i < 5; i++) { - let star_class = 'fa-star'; - if (i >= rating) star_class = 'fa-star-o'; - rating_html += ``; - } - return rating_html; -} - -export { - get_review_html, - get_rating_html -} diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js deleted file mode 100644 index 5545a4935b3..00000000000 --- a/erpnext/public/js/hub/hub_call.js +++ /dev/null @@ -1,68 +0,0 @@ -frappe.provide('hub'); -frappe.provide('erpnext.hub'); - -erpnext.hub.cache = {}; -hub.call = function call_hub_method(method, args={}, clear_cache_on_event) { // eslint-disable-line - return new Promise((resolve, reject) => { - - // cache - const key = method + JSON.stringify(args); - if (erpnext.hub.cache[key]) { - resolve(erpnext.hub.cache[key]); - } - - // cache invalidation - const clear_cache = () => delete erpnext.hub.cache[key]; - - if (!clear_cache_on_event) { - invalidate_after_5_mins(clear_cache); - } else { - erpnext.hub.on(clear_cache_on_event, () => { - clear_cache(key); - }); - } - - let res; - if (hub.is_server) { - res = frappe.call({ - method: 'hub.hub.api.' + method, - args - }); - } else { - res = frappe.call({ - method: 'erpnext.hub_node.api.call_hub_method', - args: { - method, - params: args - } - }); - } - - res.then(r => { - if (r.message) { - const response = r.message; - if (response.error) { - frappe.throw({ - title: __('Marketplace Error'), - message: response.error - }); - } - - erpnext.hub.cache[key] = response; - erpnext.hub.trigger(`response:${key}`, { response }); - resolve(response); - } - reject(r); - - }).fail(reject); - }); -}; - -function invalidate_after_5_mins(clear_cache) { - // cache invalidation after 5 minutes - const timeout = 5 * 60 * 1000; - - setTimeout(() => { - clear_cache(); - }, timeout); -} diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js deleted file mode 100644 index 9c67c1cf9f0..00000000000 --- a/erpnext/public/js/hub/hub_factory.js +++ /dev/null @@ -1,34 +0,0 @@ -frappe.provide('erpnext.hub'); - -frappe.views.MarketplaceFactory = class MarketplaceFactory extends frappe.views.Factory { - show() { - is_marketplace_disabled() - .then(disabled => { - if (disabled) { - frappe.show_not_found('Marketplace'); - return; - } - - if (frappe.pages.marketplace) { - frappe.container.change_to('marketplace'); - erpnext.hub.marketplace.refresh(); - } else { - this.make('marketplace'); - } - }); - } - - make(page_name) { - frappe.require('marketplace.bundle.js', () => { - erpnext.hub.marketplace = new erpnext.hub.Marketplace({ - parent: this.make_page(true, page_name) - }); - }); - } -}; - -function is_marketplace_disabled() { - return frappe.call({ - method: "erpnext.hub_node.doctype.marketplace_settings.marketplace_settings.is_marketplace_enabled" - }).then(r => r.message) -} diff --git a/erpnext/public/js/hub/marketplace.bundle.js b/erpnext/public/js/hub/marketplace.bundle.js deleted file mode 100644 index a1596e0043b..00000000000 --- a/erpnext/public/js/hub/marketplace.bundle.js +++ /dev/null @@ -1,225 +0,0 @@ -import Vue from 'vue/dist/vue.js'; -import './vue-plugins'; - -// components -import PageContainer from './PageContainer.vue'; -import Sidebar from './Sidebar.vue'; -import { ProfileDialog } from './components/profile_dialog'; - -// helpers -import './hub_call'; - -frappe.provide('hub'); -frappe.provide('erpnext.hub'); -frappe.provide('frappe.route'); - -frappe.utils.make_event_emitter(frappe.route); -frappe.utils.make_event_emitter(erpnext.hub); - -erpnext.hub.Marketplace = class Marketplace { - constructor({ parent }) { - this.$parent = $(parent); - this.page = parent.page; - - this.update_hub_settings().then(() => { - - this.setup_header(); - this.make_sidebar(); - this.make_body(); - this.setup_events(); - this.refresh(); - - if (!hub.is_server) { - if (!hub.is_seller_registered()) { - this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this)) - } else { - this.page.set_secondary_action('Add Users', this.show_add_user_dialog.bind(this)); - } - } - }); - } - - setup_header() { - if (hub.is_server) return; - this.page.set_title(__('Marketplace')); - } - - setup_events() { - this.$parent.on('click', '[data-route]', (e) => { - const $target = $(e.currentTarget); - const route = $target.data().route; - frappe.set_route(route); - }); - - // generic action handler - this.$parent.on('click', '[data-action]', e => { - const $target = $(e.currentTarget); - const action = $target.data().action; - - if (action && this[action]) { - this[action].apply(this, $target); - } - }) - } - - make_sidebar() { - this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); - - new Vue({ - el: $('
').appendTo(this.$sidebar)[0], - render: h => h(Sidebar) - }); - } - - make_body() { - this.$body = this.$parent.find('.layout-main-section'); - this.$page_container = $('
').appendTo(this.$body); - - new Vue({ - el: '.hub-page-container', - render: h => h(PageContainer) - }); - - if (!hub.is_server) { - erpnext.hub.on('seller-registered', () => { - this.page.clear_primary_action(); - }); - } - } - - refresh() { - - } - - show_register_dialog() { - if(frappe.session.user === 'Administrator') { - frappe.msgprint(__('You need to be a user other than Administrator with System Manager and Item Manager roles to register on Marketplace.')); - return; - } - - if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) { - frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to register on Marketplace.')); - return; - } - - this.register_dialog = ProfileDialog( - __('Become a Seller'), - { - label: __('Register'), - on_submit: this.register_marketplace.bind(this) - } - ); - - this.register_dialog.show(); - } - - register_marketplace({company, company_description}) { - frappe.call({ - method: 'erpnext.hub_node.api.register_marketplace', - args: { - company, - company_description - } - }).then((r) => { - if (r.message && r.message.ok) { - this.register_dialog.hide(); - - this.update_hub_settings() - .then(() => { - frappe.set_route('marketplace', 'publish'); - erpnext.hub.trigger('seller-registered'); - }); - } - }); - } - - show_add_user_dialog() { - if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) { - frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to add users to Marketplace.')); - return; - } - - this.get_unregistered_users() - .then(r => { - const user_list = r.message; - - const d = new frappe.ui.Dialog({ - title: __('Add Users to Marketplace'), - fields: [ - { - label: __('Users'), - fieldname: 'users', - fieldtype: 'MultiSelect', - reqd: 1, - get_data() { - return user_list; - } - } - ], - primary_action({ users }) { - const selected_users = users.split(',').map(d => d.trim()).filter(Boolean); - - if (!selected_users.every(user => user_list.includes(user))) { - d.set_df_property('users', 'description', __('Some emails are invalid')); - return; - } else { - d.set_df_property('users', 'description', ''); - } - - frappe.call('erpnext.hub_node.api.register_users', { - user_list: selected_users - }) - .then(r => { - d.hide(); - - if (r.message && r.message.length) { - frappe.show_alert(__('Added {0} users', [r.message.length])); - } - }); - } - }); - - d.show(); - }); - } - - get_unregistered_users() { - return frappe.call('erpnext.hub_node.api.get_unregistered_users') - } - - update_hub_settings() { - return hub.get_settings().then(doc => { - hub.settings = doc; - }); - } -} - -Object.assign(hub, { - is_seller_registered() { - return hub.settings.registered; - }, - - is_user_registered() { - return this.is_seller_registered() && hub.settings.users - .filter(hub_user => hub_user.user === frappe.session.user) - .length === 1; - }, - - get_settings() { - if (frappe.session.user === 'Guest') { - return Promise.resolve({ - registered: 0 - }); - } - return frappe.db.get_doc('Marketplace Settings'); - } -}); - -/** - * Returns true if list_a is subset of list_b - * @param {Array} list_a - * @param {Array} list_b - */ -function is_subset(list_a, list_b) { - return list_a.every(item => list_b.includes(item)); -} diff --git a/erpnext/public/js/hub/pages/Buying.vue b/erpnext/public/js/hub/pages/Buying.vue deleted file mode 100644 index ebf593aca4d..00000000000 --- a/erpnext/public/js/hub/pages/Buying.vue +++ /dev/null @@ -1,56 +0,0 @@ - - diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue deleted file mode 100644 index 16d06018ff0..00000000000 --- a/erpnext/public/js/hub/pages/Category.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/FeaturedItems.vue b/erpnext/public/js/hub/pages/FeaturedItems.vue deleted file mode 100644 index 8380b2b2c0b..00000000000 --- a/erpnext/public/js/hub/pages/FeaturedItems.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue deleted file mode 100644 index 8fe824566db..00000000000 --- a/erpnext/public/js/hub/pages/Home.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue deleted file mode 100644 index 93002a7b27a..00000000000 --- a/erpnext/public/js/hub/pages/Item.vue +++ /dev/null @@ -1,356 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Messages.vue b/erpnext/public/js/hub/pages/Messages.vue deleted file mode 100644 index 73506e99266..00000000000 --- a/erpnext/public/js/hub/pages/Messages.vue +++ /dev/null @@ -1,104 +0,0 @@ - - diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue deleted file mode 100644 index 8901b97802d..00000000000 --- a/erpnext/public/js/hub/pages/NotFound.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue deleted file mode 100644 index ecba4b1e5a8..00000000000 --- a/erpnext/public/js/hub/pages/Publish.vue +++ /dev/null @@ -1,212 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/PublishedItems.vue b/erpnext/public/js/hub/pages/PublishedItems.vue deleted file mode 100644 index cbb22164e6e..00000000000 --- a/erpnext/public/js/hub/pages/PublishedItems.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue deleted file mode 100644 index 7007ddcf8e7..00000000000 --- a/erpnext/public/js/hub/pages/SavedItems.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue deleted file mode 100644 index c10841e9848..00000000000 --- a/erpnext/public/js/hub/pages/Search.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue deleted file mode 100644 index 3c9b800f4a0..00000000000 --- a/erpnext/public/js/hub/pages/Seller.vue +++ /dev/null @@ -1,201 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/SellerItems.vue b/erpnext/public/js/hub/pages/SellerItems.vue deleted file mode 100644 index 852fbaee3ff..00000000000 --- a/erpnext/public/js/hub/pages/SellerItems.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Selling.vue b/erpnext/public/js/hub/pages/Selling.vue deleted file mode 100644 index 8743027885d..00000000000 --- a/erpnext/public/js/hub/pages/Selling.vue +++ /dev/null @@ -1,66 +0,0 @@ - - diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js deleted file mode 100644 index 4912d684991..00000000000 --- a/erpnext/public/js/hub/vue-plugins.js +++ /dev/null @@ -1,58 +0,0 @@ -import Vue from 'vue/dist/vue.js'; - -// Global components -import ItemCardsContainer from './components/ItemCardsContainer.vue'; -import SectionHeader from './components/SectionHeader.vue'; -import SearchInput from './components/SearchInput.vue'; -import DetailView from './components/DetailView.vue'; -import DetailHeaderItem from './components/DetailHeaderItem.vue'; -import EmptyState from './components/EmptyState.vue'; -import Image from './components/Image.vue'; - -Vue.prototype.__ = window.__; -Vue.prototype.frappe = window.frappe; - -Vue.component('item-cards-container', ItemCardsContainer); -Vue.component('section-header', SectionHeader); -Vue.component('search-input', SearchInput); -Vue.component('detail-view', DetailView); -Vue.component('detail-header-item', DetailHeaderItem); -Vue.component('empty-state', EmptyState); -Vue.component('base-image', Image); - -Vue.directive('route', { - bind(el, binding) { - const route = binding.value; - if (!route) return; - el.classList.add('cursor-pointer'); - el.dataset.route = route; - el.addEventListener('click', () => frappe.set_route(route)); - }, - unbind(el) { - el.classList.remove('cursor-pointer'); - } -}); - -const handleImage = (el, src) => { - let img = new Image(); - // add loading class - el.src = ''; - el.classList.add('img-loading'); - - img.onload = () => { - // image loaded, remove loading class - el.classList.remove('img-loading'); - // set src - el.src = src; - } - img.onerror = () => { - el.classList.remove('img-loading'); - el.classList.add('no-image'); - el.src = null; - } - img.src = src; -} - -Vue.filter('striphtml', function (text) { - return strip_html(text || ''); -}); diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json index f48fe6f4763..c32ab6bec24 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json @@ -7,7 +7,7 @@ "engine": "InnoDB", "field_order": [ "certificate_details_section", - "section_code", + "tax_withholding_category", "fiscal_year", "column_break_3", "certificate_no", @@ -33,13 +33,6 @@ "reqd": 1, "unique": 1 }, - { - "fieldname": "section_code", - "fieldtype": "Select", - "label": "Section Code", - "options": "192\n193\n194\n194A\n194C\n194D\n194H\n194I\n194J\n194LA\n194LBB\n194LBC\n195", - "reqd": 1 - }, { "fieldname": "section_break_3", "fieldtype": "Section Break", @@ -123,13 +116,22 @@ "label": "Fiscal Year", "options": "Fiscal Year", "reqd": 1 + }, + { + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category", + "reqd": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-04-23 23:04:41.203721", + "modified": "2021-10-23 18:33:38.962622", "modified_by": "Administrator", "module": "Regional", "name": "Lower Deduction Certificate", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index d8553f1d913..821e0171bbd 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -15,7 +15,7 @@ from erpnext.accounts.utils import get_fiscal_year class LowerDeductionCertificate(Document): def validate(self): self.validate_dates() - self.validate_supplier_against_section_code() + self.validate_supplier_against_tax_category() def validate_dates(self): if getdate(self.valid_upto) < getdate(self.valid_from): @@ -31,12 +31,14 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) - def validate_supplier_against_section_code(self): - duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'section_code': self.section_code}, ['name', 'valid_from', 'valid_upto'], as_dict=True) + def validate_supplier_against_tax_category(self): + duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', + {'supplier': self.supplier, 'tax_withholding_category': self.tax_withholding_category, 'name': ("!=", self.name)}, + ['name', 'valid_from', 'valid_upto'], as_dict=True) if duplicate_certificate and self.are_dates_overlapping(duplicate_certificate): certificate_link = get_link_to_form('Lower Deduction Certificate', duplicate_certificate.name) - frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against Section Code {2} for this time period.") - .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.section_code))) + frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against category {2} for this time period.") + .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.tax_withholding_category))) def are_dates_overlapping(self,duplicate_certificate): valid_from = duplicate_certificate.valid_from diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 1733220c0ac..0c87421012b 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -855,7 +855,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/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index f4c049d1623..2b5ecc3b18c 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -122,7 +122,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, sum(s.total_taxes_and_charges) + s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount) from `tabSales Invoice Item` i inner join `tabSales Invoice` s on diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index f6926906b82..d46c46f90e6 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -78,6 +78,8 @@ frappe.ui.form.on("Sales Order", { }); erpnext.queries.setup_warehouse_query(frm); + + frm.ignore_doctypes_on_cancel_all = ['Purchase Order']; }, delivery_date: function(frm) { diff --git a/erpnext/selling/print_format_field_template/__init__.py b/erpnext/selling/print_format_field_template/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/selling/print_format_field_template/quotation_taxes/__init__.py b/erpnext/selling/print_format_field_template/quotation_taxes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json b/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json new file mode 100644 index 00000000000..eaa17cedf05 --- /dev/null +++ b/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 15:48:56.416449", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Quotation", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:11:33.553722", + "modified_by": "Administrator", + "module": "Selling", + "name": "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/selling/print_format_field_template/sales_order_taxes/__init__.py b/erpnext/selling/print_format_field_template/sales_order_taxes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json b/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json new file mode 100644 index 00000000000..2aacb440ff8 --- /dev/null +++ b/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 18:04:24.443076", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Sales Order", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:04:24.443076", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales 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/selling/sales_common.js b/erpnext/selling/sales_common.js index ddd4c4e6a5a..a86e60494e8 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -206,8 +206,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran var me = this; var item = frappe.get_doc(cdt, cdn); - if (item.serial_no && item.qty === item.serial_no.split(`\n`).length) { - return; + // check if serial nos entered are as much as qty in row + if (item.serial_no) { + let serial_nos = item.serial_no.split(`\n`).filter(sn => sn.trim()); // filter out whitespaces + if (item.qty === serial_nos.length) return; } if (item.serial_no && !item.batch_no) { diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index b7e895db363..8a1338583ba 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1195,7 +1195,7 @@ "*": { "item_tax_templates": [ { - "title": "GST 9%", + "title": "GST 18%", "taxes": [ { "tax_type": { diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index c473395a9aa..d61d94c3c69 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -202,7 +202,6 @@ def install(country=None): {'doctype': "Party Type", "party_type": "Student", "account_type": "Receivable"}, {'doctype': "Party Type", "party_type": "Donor", "account_type": "Receivable"}, - {'doctype': "Opportunity Type", "name": "Hub"}, {'doctype': "Opportunity Type", "name": _("Sales")}, {'doctype': "Opportunity Type", "name": _("Support")}, {'doctype': "Opportunity Type", "name": _("Maintenance")}, diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 320cb7ba847..1412acfcead 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Projects Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"HR Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Selling Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Buying Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Support Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Shopping Cart Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Portal Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Manufacturing Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Education Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Hotel Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Domain Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Products Settings\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]", "creation": "2020-03-12 14:47:51.166455", "docstatus": 0, "doctype": "Workspace", @@ -10,7 +10,7 @@ "idx": 0, "label": "ERPNext Settings", "links": [], - "modified": "2021-08-05 12:15:59.052328", + "modified": "2021-10-26 21:32:55.323591", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", @@ -27,6 +27,14 @@ "link_to": "Projects Settings", "type": "DocType" }, + { + "color": "Grey", + "doc_view": "", + "icon": "dot-horizontal", + "label": "Naming Series", + "link_to": "Naming Series", + "type": "DocType" + }, { "icon": "accounting", "label": "Accounts Settings", diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index db5caf91645..4b314a00a42 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -17,7 +17,6 @@ "variant_of", "item_name", "item_group", - "is_item_from_hub", "stock_uom", "column_break0", "disabled", @@ -134,12 +133,7 @@ "website_specifications", "web_long_description", "website_content", - "total_projected_qty", - "hub_publishing_sb", - "publish_in_hub", - "hub_category_to_publish", - "hub_warehouse", - "synced_with_hub" + "total_projected_qty" ], "fields": [ { @@ -202,14 +196,6 @@ "reqd": 1, "search_index": 1 }, - { - "default": "0", - "depends_on": "eval:!doc.is_fixed_asset", - "fieldname": "is_item_from_hub", - "fieldtype": "Check", - "label": "Is Item from Hub", - "read_only": 1 - }, { "fieldname": "stock_uom", "fieldtype": "Link", @@ -996,41 +982,6 @@ "print_hide": 1, "read_only": 1 }, - { - "collapsible": 1, - "depends_on": "eval:(!doc.is_item_from_hub && !doc.is_fixed_asset)", - "fieldname": "hub_publishing_sb", - "fieldtype": "Section Break", - "label": "Hub Publishing Details" - }, - { - "default": "0", - "description": "Publish Item to hub.erpnext.com", - "fieldname": "publish_in_hub", - "fieldtype": "Check", - "label": "Publish in Hub" - }, - { - "fieldname": "hub_category_to_publish", - "fieldtype": "Data", - "label": "Hub Category to Publish", - "read_only": 1 - }, - { - "description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.", - "fieldname": "hub_warehouse", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Hub Warehouse", - "options": "Warehouse" - }, - { - "default": "0", - "fieldname": "synced_with_hub", - "fieldtype": "Check", - "label": "Synced With Hub", - "read_only": 1 - }, { "depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset", "fieldname": "over_delivery_receipt_allowance", @@ -1078,10 +1029,11 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 1, - "modified": "2021-08-26 12:23:07.277077", + "modified": "2021-10-27 21:04:00.324786", "modified_by": "Administrator", "module": "Stock", "name": "Item", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 8cc9f74a42a..fa42c7d934b 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -123,7 +123,6 @@ class Item(WebsiteGenerator): self.validate_barcode() self.validate_warehouse_for_reorder() self.update_bom_item_desc() - self.synced_with_hub = 0 self.validate_has_variants() self.validate_attributes_in_variants() @@ -677,6 +676,8 @@ class Item(WebsiteGenerator): def after_rename(self, old_name, new_name, merge): if merge: self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name) + frappe.msgprint(_("It can take upto few hours for accurate stock values to be visible after merging items."), + indicator="orange", title="Note") if self.route: invalidate_cache_for_item(self) diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py index 6080fb4a5fa..6f2a389b707 100644 --- a/erpnext/stock/doctype/item_alternative/item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/item_alternative.py @@ -25,19 +25,29 @@ class ItemAlternative(Document): frappe.throw(_("Alternative item must not be same as item code")) item_meta = frappe.get_meta("Item") - fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no","has_batch_no"] - item_data = frappe.db.get_values("Item", self.item_code, fields, as_dict=1) - alternative_item_data = frappe.db.get_values("Item", self.alternative_item_code, fields, as_dict=1) + fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no", "has_batch_no", "allow_alternative_item"] + item_data = frappe.db.get_value("Item", self.item_code, fields, as_dict=1) + alternative_item_data = frappe.db.get_value("Item", self.alternative_item_code, fields, as_dict=1) for field in fields: - if item_data[0].get(field) != alternative_item_data[0].get(field): + if item_data.get(field) != alternative_item_data.get(field): raise_exception, alert = [1, False] if field == "is_stock_item" else [0, True] frappe.msgprint(_("The value of {0} differs between Items {1} and {2}") \ .format(frappe.bold(item_meta.get_label(field)), frappe.bold(self.alternative_item_code), frappe.bold(self.item_code)), - alert=alert, raise_exception=raise_exception) + alert=alert, raise_exception=raise_exception, indicator="Orange") + + alternate_item_check_msg = _("Allow Alternative Item must be checked on Item {}") + + if not item_data.allow_alternative_item: + frappe.throw(alternate_item_check_msg.format(self.item_code)) + if self.two_way and not alternative_item_data.allow_alternative_item: + frappe.throw(alternate_item_check_msg.format(self.item_code)) + + + def validate_duplicate(self): if frappe.db.get_value("Item Alternative", {'item_code': self.item_code, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 17df9777b19..ec01cae2567 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -502,7 +502,8 @@ def make_stock_entry(source_name, target_doc=None): "field_map": { "name": "material_request_item", "parent": "material_request", - "uom": "stock_uom" + "uom": "stock_uom", + "job_card_item": "job_card_item" }, "postprocess": update_item, "condition": lambda doc: doc.ordered_qty < doc.stock_qty diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 25bbbbd4b3e..2bad42a0ebb 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -52,6 +52,7 @@ "sales_order_item", "production_plan", "material_request_plan_item", + "job_card_item", "col_break4", "expense_account", "section_break_46", @@ -444,16 +445,25 @@ { "fieldname": "qty_info_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "job_card_item", + "fieldtype": "Data", + "hidden": 1, + "no_copy": 1, + "print_hide": 1, + "label": "Job Card Item" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-02 11:44:36.553064", + "modified": "2021-11-03 14:40:24.409826", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index 21467935370..c604c711ef5 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -18,7 +18,9 @@ "get_item_locations", "section_break_6", "locations", - "amended_from" + "amended_from", + "print_settings_section", + "group_same_items" ], "fields": [ { @@ -110,14 +112,28 @@ "options": "STO-PICK-.YYYY.-", "reqd": 1, "set_only_once": 1 + }, + { + "fieldname": "print_settings_section", + "fieldtype": "Section Break", + "label": "Print Settings" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "group_same_items", + "fieldtype": "Check", + "label": "Group Same Items", + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-03-17 11:38:41.932875", + "modified": "2021-10-05 15:08:40.369957", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -184,4 +200,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index dffbe80fa39..4c02f3db43b 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -2,10 +2,8 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import json -from collections import OrderedDict +from collections import OrderedDict, defaultdict import frappe from frappe import _ @@ -121,6 +119,34 @@ class PickList(Document): and (self.for_qty is None or self.for_qty == 0): frappe.throw(_("Qty of Finished Goods Item should be greater than 0.")) + def before_print(self, settings=None): + if self.get("group_same_items"): + self.group_similar_items() + + def group_similar_items(self): + group_item_qty = defaultdict(float) + group_picked_qty = defaultdict(float) + + for item in self.locations: + group_item_qty[(item.item_code, item.warehouse)] += item.qty + group_picked_qty[(item.item_code, item.warehouse)] += item.picked_qty + + duplicate_list = [] + for item in self.locations: + if (item.item_code, item.warehouse) in group_item_qty: + item.qty = group_item_qty[(item.item_code, item.warehouse)] + item.picked_qty = group_picked_qty[(item.item_code, item.warehouse)] + item.stock_qty = group_item_qty[(item.item_code, item.warehouse)] + del group_item_qty[(item.item_code, item.warehouse)] + else: + duplicate_list.append(item) + + for item in duplicate_list: + self.remove(item) + + for idx, item in enumerate(self.locations, start=1): + item.idx = idx + def validate_item_locations(pick_list): if not pick_list.locations: diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index fd0b3680df2..58b46e1eefc 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe import _dict test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] @@ -356,6 +357,39 @@ class TestPickList(ERPNextTestCase): sales_order.cancel() purchase_receipt.cancel() + def test_pick_list_grouping_before_print(self): + def _compare_dicts(a, b): + "compare dicts but ignore missing keys in `a`" + for key, value in a.items(): + self.assertEqual(b.get(key), value, msg=f"{key} doesn't match") + + # nothing should be grouped + pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[ + _dict(item_code="A", warehouse="X", qty=1, picked_qty=2), + _dict(item_code="B", warehouse="X", qty=1, picked_qty=2), + _dict(item_code="A", warehouse="Y", qty=1, picked_qty=2), + _dict(item_code="B", warehouse="Y", qty=1, picked_qty=2), + ]) + pl.before_print() + self.assertEqual(len(pl.locations), 4) + + # grouping should halve the number of items + pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[ + _dict(item_code="A", warehouse="X", qty=5, picked_qty=1), + _dict(item_code="B", warehouse="Y", qty=4, picked_qty=2), + _dict(item_code="A", warehouse="X", qty=3, picked_qty=2), + _dict(item_code="B", warehouse="Y", qty=2, picked_qty=2), + ]) + pl.before_print() + self.assertEqual(len(pl.locations), 2) + + expected_items = [ + _dict(item_code="A", warehouse="X", qty=8, picked_qty=3), + _dict(item_code="B", warehouse="Y", qty=6, picked_qty=4), + ] + for expected_item, created_item in zip(expected_items, pl.locations): + _compare_dicts(expected_item, created_item) + # def test_pick_list_skips_items_in_expired_batch(self): # pass diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ac8303eda3d..c4b8131305e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -88,7 +88,11 @@ frappe.ui.form.on('Stock Entry', { } } - filters["warehouse"] = item.s_warehouse || item.t_warehouse; + // User could want to select a manually created empty batch (no warehouse) + // or a pre-existing batch + if (frm.doc.purpose != "Material Receipt") { + filters["warehouse"] = item.s_warehouse || item.t_warehouse; + } return { query : "erpnext.controllers.queries.get_batch_no", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cbff2149d64..e0190b64a75 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -89,7 +89,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse, args.company)) + if doc and doc.get('doctype') == 'Purchase Order': + # calculate company_total_stock only for po + bin_details = get_bin_details(args.item_code, out.warehouse, args.company) + else: + bin_details = get_bin_details(args.item_code, out.warehouse) + + out.update(bin_details) # update args with out, if key or value not exists for key, value in iteritems(out): @@ -485,8 +491,9 @@ def get_item_tax_template(args, item, out): "item_tax_template": None } """ - item_tax_template = args.get("item_tax_template") - item_tax_template = _get_item_tax_template(args, item.taxes, out) + item_tax_template = None + if item.taxes: + item_tax_template = _get_item_tax_template(args, item.taxes, out) if not item_tax_template: item_group = item.item_group @@ -502,17 +509,17 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): taxes_with_no_validity = [] for tax in taxes: - tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company') - if (tax.valid_from or tax.maximum_net_rate) and tax_company == args['company']: - # In purchase Invoice first preference will be given to supplier invoice date - # if supplier date is not present then posting date - validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') + tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, 'company') + if tax_company == args['company']: + if (tax.valid_from or tax.maximum_net_rate): + # In purchase Invoice first preference will be given to supplier invoice date + # if supplier date is not present then posting date + validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') - if getdate(tax.valid_from) <= getdate(validation_date) \ - and is_within_valid_range(args, tax): - taxes_with_validity.append(tax) - else: - if tax_company == args['company']: + if getdate(tax.valid_from) <= getdate(validation_date) \ + and is_within_valid_range(args, tax): + taxes_with_validity.append(tax) + else: taxes_with_no_validity.append(tax) if taxes_with_validity: @@ -890,8 +897,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa res[fieldname] = pos_profile.get(fieldname) if res.get("warehouse"): - res.actual_qty = get_bin_details(args.item_code, - res.warehouse).get("actual_qty") + res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty") return res diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e9d5b6ae091..70f4bcaef79 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -123,12 +123,11 @@ def set_as_cancel(voucher_type, voucher_no): (now(), frappe.session.user, voucher_type, voucher_no)) def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): - args.update({"doctype": "Stock Ledger Entry"}) + args["doctype"] = "Stock Ledger Entry" sle = frappe.get_doc(args) sle.flags.ignore_permissions = 1 sle.allow_negative_stock=allow_negative_stock sle.via_landed_cost_voucher = via_landed_cost_voucher - sle.insert() sle.submit() return sle @@ -601,7 +600,7 @@ class update_entries_after(object): if not allow_zero_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_incoming_value_for_serial_nos(self, sle, serial_nos): # get rate from serial nos within same company @@ -668,7 +667,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_fifo_values(self, sle): incoming_rate = flt(sle.incoming_rate) @@ -701,7 +700,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: _rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) else: _rate = 0 @@ -912,10 +911,11 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True): - # Get valuation rate from last sle for the same item and warehouse - if not company: - company = erpnext.get_default_company() + if not company: + company = frappe.get_cached_value("Warehouse", warehouse, "company") + + # Get valuation rate from last sle for the same item and warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` force index (item_warehouse) where diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index c4a0497b744..e1d5a89082e 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -101,11 +101,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None if with_valuation_rate: if with_serial_no: - serial_nos = last_entry.get("serial_no") - - if (serial_nos and - len(get_serial_nos_data(serial_nos)) < last_entry.qty_after_transaction): - serial_nos = get_serial_nos_data_after_transactions(args) + serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) if last_entry else (0.0, 0.0, 0.0)) @@ -115,19 +111,32 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None return last_entry.qty_after_transaction if last_entry else 0.0 def get_serial_nos_data_after_transactions(args): - serial_nos = [] - data = frappe.db.sql(""" SELECT serial_no, actual_qty - FROM `tabStock Ledger Entry` - WHERE - item_code = %(item_code)s and warehouse = %(warehouse)s - and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s) - order by posting_date, posting_time asc """, args, as_dict=1) + from pypika import CustomFunction - for d in data: - if d.actual_qty > 0: - serial_nos.extend(get_serial_nos_data(d.serial_no)) + serial_nos = set() + args = frappe._dict(args) + sle = frappe.qb.DocType('Stock Ledger Entry') + Timestamp = CustomFunction('timestamp', ['date', 'time']) + + stock_ledger_entries = frappe.qb.from_( + sle + ).select( + 'serial_no','actual_qty' + ).where( + (sle.item_code == args.item_code) + & (sle.warehouse == args.warehouse) + & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)) + & (sle.is_cancelled == 0) + ).orderby( + sle.posting_date, sle.posting_time, sle.creation + ).run(as_dict=1) + + for stock_ledger_entry in stock_ledger_entries: + changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no) + if stock_ledger_entry.actual_qty > 0: + serial_nos.update(changed_serial_no) else: - serial_nos = list(set(serial_nos) - set(get_serial_nos_data(d.serial_no))) + serial_nos.difference_update(changed_serial_no) return '\n'.join(serial_nos) diff --git a/erpnext/templates/print_formats/includes/taxes_and_charges.html b/erpnext/templates/print_formats/includes/taxes_and_charges.html new file mode 100644 index 00000000000..0d8e3834d81 --- /dev/null +++ b/erpnext/templates/print_formats/includes/taxes_and_charges.html @@ -0,0 +1,34 @@ +{% macro render_row(label, value) %} +
+
+
{{ label }}
+
+
+ {{ value }} +
+
+{% endmacro %} + +{%- macro render_discount_amount(doc) -%} + {%- if doc.discount_amount -%} + {{ render_row(_(doc.meta.get_label('discount_amount')), '- ' + doc.get_formatted("discount_amount", doc)) }} + {%- endif -%} +{%- endmacro -%} + +
+
+
+ {%- if doc.apply_discount_on == "Net Total" -%} + {{ render_discount_amount(doc) }} + {%- endif -%} + {%- for charge in doc.taxes -%} + {%- if (charge.tax_amount or print_settings.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%} + {{ render_row(charge.get_formatted("description"), charge.get_formatted('tax_amount', doc)) }} + {%- endif -%} + {%- endfor -%} + {%- if doc.apply_discount_on == "Grand Total" -%} + {{ render_discount_amount(doc) }} + {%- endif -%} +
+
+ diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py index 881f286baeb..3ce68d89bcd 100644 --- a/erpnext/tests/test_woocommerce.py +++ b/erpnext/tests/test_woocommerce.py @@ -12,12 +12,6 @@ from erpnext.erpnext_integrations.connectors.woocommerce_connection import order class TestWoocommerce(unittest.TestCase): def setUp(self): - if not frappe.db.exists('Company', 'Woocommerce'): - company = frappe.new_doc("Company") - company.company_name = "Woocommerce" - company.abbr = "W" - company.default_currency = "INR" - company.save() woo_settings = frappe.get_doc("Woocommerce Settings") if not woo_settings.secret: @@ -26,14 +20,14 @@ class TestWoocommerce(unittest.TestCase): woo_settings.api_consumer_key = "ck_fd43ff5756a6abafd95fadb6677100ce95a758a1" woo_settings.api_consumer_secret = "cs_94360a1ad7bef7fa420a40cf284f7b3e0788454e" woo_settings.enable_sync = 1 - woo_settings.company = "Woocommerce" - woo_settings.tax_account = "Sales Expenses - W" - woo_settings.f_n_f_account = "Expenses - W" + woo_settings.company = "_Test Company" + woo_settings.tax_account = "Sales Expenses - _TC" + woo_settings.f_n_f_account = "Expenses - _TC" woo_settings.creation_user = "Administrator" woo_settings.save(ignore_permissions=True) def test_sales_order_for_woocommerce(self): - frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} + frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"_Test Company","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} order() self.assertTrue(frappe.get_value("Customer",{"woocommerce_email":"tony@gmail.com"}))