diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 3521191ab6c..48b461448a7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -15,6 +15,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ return (doc.qty<=doc.received_qty) ? "green" : "orange"; }); } + + this.frm.set_query("inter_company_account", function() { + return { + filters: { + company: doc.company, + is_group: 0, + root_type: "Liability", + } + }; + }); }, company: function() { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 8925b87b52b..369f3d0e158 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -126,6 +126,7 @@ "write_off_cost_center", "advances_section", "allocate_advances_automatically", + "adjust_advance_taxes", "get_advances", "advances", "payment_schedule_section", @@ -151,9 +152,11 @@ "is_opening", "against_expense_account", "column_break_63", + "inter_company_account", "status", "inter_company_invoice_reference", "is_internal_supplier", + "represents_company", "remarks", "subscription_section", "from_date", @@ -1222,7 +1225,7 @@ "fieldtype": "Select", "in_standard_filter": 1, "label": "Status", - "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled", + "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1 }, { @@ -1329,12 +1332,35 @@ "fieldtype": "Link", "label": "Project", "options": "Project" + }, + { + "default": "0", + "description": "Taxes paid while advance payment will be adjusted against this invoice", + "fieldname": "adjust_advance_taxes", + "fieldtype": "Check", + "label": "Adjust Advance Taxes" + }, + { + "depends_on": "eval:doc.is_internal_supplier", + "fieldname": "inter_company_account", + "fieldtype": "Link", + "label": "Inter Company Account", + "options": "Account" + }, + { + "depends_on": "eval:doc.is_internal_supplier", + "fetch_from": "supplier.represents_company", + "fieldname": "represents_company", + "fieldtype": "Link", + "label": "Represents Company", + "options": "Company" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, - "modified": "2020-09-21 12:22:09.164068", + "links": [], + "modified": "2020-11-27 19:47:04.827315", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 91c4dfb5877..b4e4a7db40a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -102,6 +102,7 @@ class PurchaseInvoice(BuyingController): self.set_status() self.validate_purchase_receipt_if_update_stock() validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference) + self.set_inter_company_account() def validate_release_date(self): if self.release_date and getdate(nowdate()) >= getdate(self.release_date): @@ -371,6 +372,26 @@ class PurchaseInvoice(BuyingController): where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)""" }) + def set_inter_company_account(self): + """ + Set intercompany account for inter warehouse transactions + This account will be used in case billing company and internal supplier's + representation company is same + """ + + if self.is_internal_transfer() and not self.inter_company_account: + self.inter_company_account = frappe.get_cached_value('Company', self.company, 'default_inter_company_account') + + def is_internal_transfer(self): + """ + It will an internal transfer if its an internal supplier and representation + company is same as billing company + """ + if self.is_internal_supplier and (self.represents_company == self.company): + return True + + return False + def validate_purchase_receipt_if_update_stock(self): if self.update_stock: for item in self.get("items"): @@ -444,6 +465,7 @@ class PurchaseInvoice(BuyingController): self.get_asset_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) + self.make_internal_transfer_gl_entries(gl_entries) gl_entries = make_regional_gl_entries(gl_entries, self) @@ -469,211 +491,212 @@ class PurchaseInvoice(BuyingController): # because rounded_total had value even before introcution of posting GLE based on rounded total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total - if grand_total: - # Didnot use base_grand_total to book rounding loss gle - grand_total_in_company_currency = flt(grand_total * self.conversion_rate, - self.precision("grand_total")) - gl_entries.append( - self.get_gl_dict({ - "account": self.credit_to, - "party_type": "Supplier", - "party": self.supplier, - "due_date": self.due_date, - "against": self.against_expense_account, - "credit": grand_total_in_company_currency, - "credit_in_account_currency": grand_total_in_company_currency \ - if self.party_account_currency==self.company_currency else grand_total, - "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, - "against_voucher_type": self.doctype, - "project": self.project, - "cost_center": self.cost_center - }, self.party_account_currency, item=self) - ) + if grand_total and not self.is_internal_transfer(): + # Didnot use base_grand_total to book rounding loss gle + grand_total_in_company_currency = flt(grand_total * self.conversion_rate, + self.precision("grand_total")) + gl_entries.append( + self.get_gl_dict({ + "account": self.credit_to, + "party_type": "Supplier", + "party": self.supplier, + "due_date": self.due_date, + "against": self.against_expense_account, + "credit": grand_total_in_company_currency, + "credit_in_account_currency": grand_total_in_company_currency \ + if self.party_account_currency==self.company_currency else grand_total, + "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, + "against_voucher_type": self.doctype, + "project": self.project, + "cost_center": self.cost_center + }, self.party_account_currency, item=self) + ) def make_item_gl_entries(self, gl_entries): # item gl entries - stock_items = self.get_stock_items() - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - if self.update_stock and self.auto_accounting_for_stock: - warehouse_account = get_warehouse_account_map(self.company) + if not self.is_internal_transfer(): + stock_items = self.get_stock_items() + expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + if self.update_stock and self.auto_accounting_for_stock: + warehouse_account = get_warehouse_account_map(self.company) - landed_cost_entries = get_item_account_wise_additional_cost(self.name) + landed_cost_entries = get_item_account_wise_additional_cost(self.name) - voucher_wise_stock_value = {} - if self.update_stock: - for d in frappe.get_all('Stock Ledger Entry', - fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}): - voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference) + voucher_wise_stock_value = {} + if self.update_stock: + for d in frappe.get_all('Stock Ledger Entry', + fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}): + voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference) - valuation_tax_accounts = [d.account_head for d in self.get("taxes") - if d.category in ('Valuation', 'Total and Valuation') - and flt(d.base_tax_amount_after_discount_amount)] + valuation_tax_accounts = [d.account_head for d in self.get("taxes") + if d.category in ('Valuation', 'Total and Valuation') + and flt(d.base_tax_amount_after_discount_amount)] - for item in self.get("items"): - if flt(item.base_net_amount): - account_currency = get_account_currency(item.expense_account) - if item.item_code: - asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") + for item in self.get("items"): + if flt(item.base_net_amount): + account_currency = get_account_currency(item.expense_account) + if item.item_code: + asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items: - # warehouse account - warehouse_debit_amount = self.make_stock_adjustment_entry(gl_entries, - item, voucher_wise_stock_value, account_currency) + if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items: + # warehouse account + warehouse_debit_amount = self.make_stock_adjustment_entry(gl_entries, + item, voucher_wise_stock_value, account_currency) - if item.from_warehouse: + if item.from_warehouse: - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[item.warehouse]['account'], - "against": warehouse_account[item.from_warehouse]["account"], - "cost_center": item.cost_center, - "project": item.project or self.project, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": warehouse_debit_amount, - }, warehouse_account[item.warehouse]["account_currency"], item=item)) - - # Intentionally passed negative debit amount to avoid incorrect GL Entry validation - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[item.from_warehouse]['account'], - "against": warehouse_account[item.warehouse]["account"], - "cost_center": item.cost_center, - "project": item.project or self.project, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")), - }, warehouse_account[item.from_warehouse]["account_currency"], item=item)) - - gl_entries.append( - self.get_gl_dict({ - "account": item.expense_account, - "against": self.supplier, - "debit": flt(item.base_net_amount, item.precision("base_net_amount")), - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "cost_center": item.cost_center, - "project": item.project - }, account_currency, item=item) - ) - - else: - gl_entries.append( - self.get_gl_dict({ - "account": item.expense_account, - "against": self.supplier, - "debit": warehouse_debit_amount, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "cost_center": item.cost_center, - "project": item.project or self.project - }, account_currency, item=item) - ) - - # Amount added through landed-cost-voucher - if landed_cost_entries: - for account, amount in iteritems(landed_cost_entries[(item.item_code, item.name)]): gl_entries.append(self.get_gl_dict({ - "account": account, + "account": warehouse_account[item.warehouse]['account'], + "against": warehouse_account[item.from_warehouse]["account"], + "cost_center": item.cost_center, + "project": item.project or self.project, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": warehouse_debit_amount, + }, warehouse_account[item.warehouse]["account_currency"], item=item)) + + # Intentionally passed negative debit amount to avoid incorrect GL Entry validation + gl_entries.append(self.get_gl_dict({ + "account": warehouse_account[item.from_warehouse]['account'], + "against": warehouse_account[item.warehouse]["account"], + "cost_center": item.cost_center, + "project": item.project or self.project, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")), + }, warehouse_account[item.from_warehouse]["account_currency"], item=item)) + + gl_entries.append( + self.get_gl_dict({ + "account": item.expense_account, + "against": self.supplier, + "debit": flt(item.base_net_amount, item.precision("base_net_amount")), + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "cost_center": item.cost_center, + "project": item.project + }, account_currency, item=item) + ) + + else: + gl_entries.append( + self.get_gl_dict({ + "account": item.expense_account, + "against": self.supplier, + "debit": warehouse_debit_amount, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item) + ) + + # Amount added through landed-cost-voucher + if landed_cost_entries: + for account, amount in iteritems(landed_cost_entries[(item.item_code, item.name)]): + gl_entries.append(self.get_gl_dict({ + "account": account, + "against": item.expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(amount), + "project": item.project or self.project + }, item=item)) + + # sub-contracting warehouse + if flt(item.rm_supp_cost): + supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"] + if not supplier_warehouse_account: + frappe.throw(_("Please set account in Warehouse {0}") + .format(self.supplier_warehouse)) + gl_entries.append(self.get_gl_dict({ + "account": supplier_warehouse_account, "against": item.expense_account, "cost_center": item.cost_center, + "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(amount), + "credit": flt(item.rm_supp_cost) + }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) + + elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)): + expense_account = (item.expense_account + if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) + + if not item.is_fixed_asset: + amount = flt(item.base_net_amount, item.precision("base_net_amount")) + else: + amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) + + auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) + + if auto_accounting_for_non_stock_items: + service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") + + if item.purchase_receipt: + # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt + expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0, + 'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail, + 'account':service_received_but_not_billed_account}, ['name']) + + if expense_booked_in_pr: + expense_account = service_received_but_not_billed_account + + gl_entries.append(self.get_gl_dict({ + "account": expense_account, + "against": self.supplier, + "debit": amount, + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item)) + + # If asset is bought through this document and not linked to PR + if self.update_stock and item.landed_cost_voucher_amount: + expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") + # Amount added through landed-cost-voucher + gl_entries.append(self.get_gl_dict({ + "account": expenses_included_in_asset_valuation, + "against": expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), "project": item.project or self.project }, item=item)) - # sub-contracting warehouse - if flt(item.rm_supp_cost): - supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"] - if not supplier_warehouse_account: - frappe.throw(_("Please set account in Warehouse {0}") - .format(self.supplier_warehouse)) - gl_entries.append(self.get_gl_dict({ - "account": supplier_warehouse_account, - "against": item.expense_account, - "cost_center": item.cost_center, - "project": item.project or self.project, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.rm_supp_cost) - }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) + gl_entries.append(self.get_gl_dict({ + "account": expense_account, + "against": expenses_included_in_asset_valuation, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project or self.project + }, item=item)) - elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)): - expense_account = (item.expense_account - if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) - - if not item.is_fixed_asset: - amount = flt(item.base_net_amount, item.precision("base_net_amount")) - else: - amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) - - auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) - - if auto_accounting_for_non_stock_items: - service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") - - if item.purchase_receipt: - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0, - 'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail, - 'account':service_received_but_not_billed_account}, ['name']) - - if expense_booked_in_pr: - expense_account = service_received_but_not_billed_account - - gl_entries.append(self.get_gl_dict({ - "account": expense_account, - "against": self.supplier, - "debit": amount, - "cost_center": item.cost_center, - "project": item.project or self.project - }, account_currency, item=item)) - - # If asset is bought through this document and not linked to PR - if self.update_stock and item.landed_cost_voucher_amount: - expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") - # Amount added through landed-cost-voucher - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_asset_valuation, - "against": expense_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project - }, item=item)) - - gl_entries.append(self.get_gl_dict({ - "account": expense_account, - "against": expenses_included_in_asset_valuation, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project - }, item=item)) - - # update gross amount of asset bought through this document - assets = frappe.db.get_all('Asset', - filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } - ) - for asset in assets: - frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) - frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) - - if self.auto_accounting_for_stock and self.is_opening == "No" and \ - item.item_code in stock_items and item.item_tax_amount: - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - if item.purchase_receipt and valuation_tax_accounts: - negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""", - (item.purchase_receipt, valuation_tax_accounts)) - - if not negative_expense_booked_in_pr: - gl_entries.append( - self.get_gl_dict({ - "account": self.stock_received_but_not_billed, - "against": self.supplier, - "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), - "remarks": self.remarks or "Accounting Entry for Stock", - "cost_center": self.cost_center, - "project": item.project or self.project - }, item=item) + # update gross amount of asset bought through this document + assets = frappe.db.get_all('Asset', + filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) - self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ - item.precision("item_tax_amount")) + if self.auto_accounting_for_stock and self.is_opening == "No" and \ + item.item_code in stock_items and item.item_tax_amount: + # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt + if item.purchase_receipt and valuation_tax_accounts: + negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry` + where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""", + (item.purchase_receipt, valuation_tax_accounts)) + + if not negative_expense_booked_in_pr: + gl_entries.append( + self.get_gl_dict({ + "account": self.stock_received_but_not_billed, + "against": self.supplier, + "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), + "remarks": self.remarks or "Accounting Entry for Stock", + "cost_center": self.cost_center, + "project": item.project or self.project + }, item=item) + ) + + self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ + item.precision("item_tax_amount")) def get_asset_gl_entry(self, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") @@ -827,7 +850,8 @@ class PurchaseInvoice(BuyingController): }, account_currency, item=tax) ) # accumulate valuation tax - if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): + if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \ + and not self.is_internal_transfer(): if self.auto_accounting_for_stock and not tax.cost_center: frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) valuation_tax.setdefault(tax.name, 0) @@ -871,8 +895,19 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "credit": valuation_tax[tax.name], "remarks": self.remarks or "Accounting Entry for Stock" - }, item=tax) - ) + }, item=tax)) + + def make_internal_transfer_gl_entries(self, gl_entries): + if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges): + account_currency = get_account_currency(self.inter_company_account) + gl_entries.append( + self.get_gl_dict({ + "account": self.inter_company_account, + "against": self.supplier, + "credit": flt(self.total_taxes_and_charges), + "credit_in_account_currency": flt(self.base_total_taxes_and_charges), + "cost_center": self.cost_center + }, account_currency, item=self)) def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries @@ -1088,7 +1123,9 @@ class PurchaseInvoice(BuyingController): if self.docstatus == 2: status = "Cancelled" elif self.docstatus == 1: - if outstanding_amount > 0 and due_date < nowdate: + if self.is_internal_transfer(): + self.status = 'Internal Transfer' + elif outstanding_amount > 0 and due_date < nowdate: self.status = "Overdue" elif outstanding_amount > 0 and due_date >= nowdate: self.status = "Unpaid" diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 86c2e408c0b..f942fc53b00 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -4,7 +4,7 @@ // render frappe.listview_settings['Purchase Invoice'] = { add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", - "currency", "is_return", "release_date", "on_hold"], + "currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"], get_indicator: function(doc) { if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') { return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"]; @@ -20,6 +20,8 @@ frappe.listview_settings['Purchase Invoice'] = { } } else if(cint(doc.is_return)) { return [__("Return"), "darkgrey", "is_return,=,Yes"]; + } else if(doc.company == doc.represents_company && doc.is_internal_supplier) { + return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"]; } else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) { return [__("Paid"), "green", "outstanding_amount,=,0"]; } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 26e5ecdfb2d..056928e21e3 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -575,6 +575,25 @@ frappe.ui.form.on('Sales Invoice', { }; }); + frm.set_query("cost_center", function() { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query("inter_company_account", function() { + return { + filters: { + company: frm.doc.company, + is_group: 0, + root_type: "Liability", + } + }; + }); + frm.custom_make_buttons = { 'Delivery Note': 'Delivery', 'Sales Invoice': 'Sales Return', @@ -1075,7 +1094,7 @@ var get_drugs_to_invoice = function(frm) { description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', get_query: function(doc) { return { - filters: { + filters: { patient: dialog.get_value("patient"), company: frm.doc.company, docstatus: 1 diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index ae401538907..7d2c4a67125 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -157,6 +157,7 @@ "more_information", "inter_company_invoice_reference", "is_internal_customer", + "represents_company", "customer_group", "campaign", "is_discounted", @@ -170,6 +171,7 @@ "c_form_applicable", "c_form_no", "column_break8", + "inter_company_account", "remarks", "sales_team_section_break", "sales_partner", @@ -1654,7 +1656,7 @@ "in_standard_filter": 1, "label": "Status", "no_copy": 1, - "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled", + "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1, "read_only": 1 }, @@ -1949,13 +1951,29 @@ "fieldtype": "Data", "label": "Company Tax ID", "read_only": 1 + }, + { + "depends_on": "eval:doc.is_internal_customer", + "fieldname": "inter_company_account", + "fieldtype": "Link", + "label": "Inter Company Account", + "options": "Account" + }, + { + "depends_on": "eval:doc.is_internal_customer", + "fetch_from": "cusstomer.represents_company", + "fieldname": "represents_company", + "fieldtype": "Link", + "label": "Represents Company", + "options": "Company", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, "links": [], - "modified": "2020-10-09 15:59:57.544736", + "modified": "2020-11-27 18:48:15.012300", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 14828bd1fa1..8b912d04c31 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -89,6 +89,7 @@ class SalesInvoice(SellingController): self.set_income_account_for_fixed_assets() self.validate_item_cost_centers() validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference) + self.set_inter_company_account() if cint(self.is_pos): self.validate_pos() @@ -570,6 +571,26 @@ class SalesInvoice(SellingController): if not res: throw(_("Customer {0} does not belong to project {1}").format(self.customer,self.project)) + def set_inter_company_account(self): + """ + Set intercompany account for inter warehouse transactions + This account will be used in case billing company and internal customer's + representation company is same + """ + + if self.is_internal_transfer() and not self.inter_company_account: + self.inter_company_account = frappe.get_cached_value('Company', self.company, 'default_inter_company_account') + + def is_internal_transfer(self): + """ + It will an internal transfer if its an internal customer and representation + company is same as billing company + """ + if self.is_internal_customer and (self.represents_company == self.company): + return True + + return False + def validate_pos(self): if self.is_return: invoice_total = self.rounded_total or self.grand_total @@ -751,6 +772,7 @@ class SalesInvoice(SellingController): self.make_customer_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) + self.make_internal_transfer_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries) @@ -770,7 +792,7 @@ class SalesInvoice(SellingController): # Checked both rounding_adjustment and rounded_total # because rounded_total had value even before introcution of posting GLE based on rounded total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total - if grand_total: + if grand_total and not self.is_internal_transfer(): # Didnot use base_grand_total to book rounding loss gle grand_total_in_company_currency = flt(grand_total * self.conversion_rate, self.precision("grand_total")) @@ -809,44 +831,57 @@ class SalesInvoice(SellingController): }, account_currency, item=tax) ) + def make_internal_transfer_gl_entries(self, gl_entries): + if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges): + account_currency = get_account_currency(self.inter_company_account) + gl_entries.append( + self.get_gl_dict({ + "account": self.inter_company_account, + "against": self.customer, + "debit": flt(self.total_taxes_and_charges), + "debit_in_account_currency": flt(self.base_total_taxes_and_charges), + "cost_center": self.cost_center + }, account_currency, item=self)) + def make_item_gl_entries(self, gl_entries): # income account gl entries - for item in self.get("items"): - if flt(item.base_net_amount, item.precision("base_net_amount")): - if item.is_fixed_asset: - asset = frappe.get_doc("Asset", item.asset) + if not self.is_internal_transfer(): + for item in self.get("items"): + if flt(item.base_net_amount, item.precision("base_net_amount")): + if item.is_fixed_asset: + asset = frappe.get_doc("Asset", item.asset) - if (len(asset.finance_books) > 1 and not item.finance_book - and asset.finance_books[0].finance_book): - frappe.throw(_("Select finance book for the item {0} at row {1}") - .format(item.item_code, item.idx)) + if (len(asset.finance_books) > 1 and not item.finance_book + and asset.finance_books[0].finance_book): + frappe.throw(_("Select finance book for the item {0} at row {1}") + .format(item.item_code, item.idx)) - fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset, - item.base_net_amount, item.finance_book) + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset, + item.base_net_amount, item.finance_book) - for gle in fixed_asset_gl_entries: - gle["against"] = self.customer - gl_entries.append(self.get_gl_dict(gle, item=item)) + for gle in fixed_asset_gl_entries: + gle["against"] = self.customer + gl_entries.append(self.get_gl_dict(gle, item=item)) - asset.db_set("disposal_date", self.posting_date) - asset.set_status("Sold" if self.docstatus==1 else None) - else: - income_account = (item.income_account - if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) + asset.db_set("disposal_date", self.posting_date) + asset.set_status("Sold" if self.docstatus==1 else None) + else: + income_account = (item.income_account + if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) - account_currency = get_account_currency(income_account) - gl_entries.append( - self.get_gl_dict({ - "account": income_account, - "against": self.customer, - "credit": flt(item.base_net_amount, item.precision("base_net_amount")), - "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) - if account_currency==self.company_currency - else flt(item.net_amount, item.precision("net_amount"))), - "cost_center": item.cost_center, - "project": item.project or self.project - }, account_currency, item=item) - ) + account_currency = get_account_currency(income_account) + gl_entries.append( + self.get_gl_dict({ + "account": income_account, + "against": self.customer, + "credit": flt(item.base_net_amount, item.precision("base_net_amount")), + "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) + if account_currency==self.company_currency + else flt(item.net_amount, item.precision("net_amount"))), + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item) + ) # expense account gl entries if cint(self.update_stock) and \ @@ -1258,7 +1293,9 @@ class SalesInvoice(SellingController): if self.docstatus == 2: status = "Cancelled" elif self.docstatus == 1: - if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': + if self.is_internal_transfer(): + self.status = 'Internal Transfer' + elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': self.status = "Overdue and Discounted" elif outstanding_amount > 0 and due_date < nowdate: self.status = "Overdue" @@ -1522,9 +1559,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): if doctype in ["Sales Invoice", "Sales Order"]: source_doc = frappe.get_doc(doctype, source_name) target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order" + source_document_warehouse_field = 'target_warehouse' + target_document_warehouse_field = 'from_warehouse' else: source_doc = frappe.get_doc(doctype, source_name) target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order" + source_document_warehouse_field = 'from_warehouse' + target_document_warehouse_field = 'target_warehouse' validate_inter_company_transaction(source_doc, doctype) details = get_inter_company_details(source_doc, doctype) @@ -1551,6 +1592,24 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): if currency: target_doc.currency = currency + item_field_map = { + "doctype": target_doctype + " Item", + "field_no_map": [ + "income_account", + "expense_account", + "cost_center", + "warehouse" + ] + } + + if source_doc.get('update_stock'): + item_field_map.update({ + 'field_map': { + source_document_warehouse_field: target_document_warehouse_field + } + }) + + doclist = get_mapped_doc(doctype, source_name, { doctype: { "doctype": target_doctype, @@ -1559,15 +1618,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): "taxes_and_charges" ] }, - doctype +" Item": { - "doctype": target_doctype + " Item", - "field_no_map": [ - "income_account", - "expense_account", - "cost_center", - "warehouse" - ] - } + doctype +" Item": item_field_map }, target_doc, set_missing_values) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js index 05d49df711a..41140d19388 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js @@ -14,8 +14,8 @@ frappe.listview_settings['Sales Invoice'] = { "Credit Note Issued": "darkgrey", "Unpaid and Discounted": "orange", "Overdue and Discounted": "red", - "Overdue": "red" - + "Overdue": "red", + "Internal Transfer": "darkgrey" }; return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; }, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2d2fff8fd54..346f7539918 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -77,7 +77,7 @@ class StockController(AccountsController): if sle_list: for sle in sle_list: if warehouse_account.get(sle.warehouse): - # from warehouse account/ target warehouse account + # from warehouse account self.check_expense_account(item_row) @@ -102,9 +102,14 @@ class StockController(AccountsController): "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", }, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) - # expense account + # expense account/ target_warehouse / source_warehouse + if item_row.target_warehouse: + expense_account = warehouse_account[item_row.target_warehouse]["account"] + else: + expense_account = item_row.expense_account + gl_list.append(self.get_gl_dict({ - "account": item_row.expense_account, + "account": expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, "project": item_row.project or self.get('project'), diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 81d07c1327e..5f3fe89bfb8 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -519,6 +519,17 @@ class calculate_taxes_and_totals(object): if self.doc.docstatus == 0: self.calculate_outstanding_amount() + def is_internal_invoice(self): + """ + Checks if its an internal transfer invoice + and decides if to calculate any out standing amount or not + """ + + if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer(): + return True + + return False + def calculate_outstanding_amount(self): # NOTE: # write_off_amount is only for POS Invoice @@ -526,7 +537,8 @@ class calculate_taxes_and_totals(object): if self.doc.doctype == "Sales Invoice": self.calculate_paid_amount() - if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): return + if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \ + self.is_internal_invoice(): return self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"]) self._set_in_company_currency(self.doc, ['write_off_amount']) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 99f3995a662..29f30763ec1 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -609,6 +609,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.calculate_outstanding_amount(update_paid_amount); }, + is_internal_invoice: function() { + if (['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) { + if (this.frm.doc.company === this.frm.doc.represents_company) { + return true + } + } + return false + }, + calculate_outstanding_amount: function(update_paid_amount) { // NOTE: // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice @@ -617,7 +626,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.calculate_paid_amount(); } - if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return; + if(this.frm.doc.is_return || (this.frm.doc.docstatus > 0) || this.is_internal_invoice()) return; frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index f882db60c5d..96d01cbc99e 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -262,7 +262,8 @@ erpnext.company.setup_queries = function(frm) { ["default_employee_advance_account", {"root_type": "Asset"}], ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], - ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}] + ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], + ["default_inter_company_account", {"root_type": "Liability"}] ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 40938ea0a5e..4dddff3a05b 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -46,10 +46,9 @@ "round_off_account", "round_off_cost_center", "write_off_account", - "discount_allowed_account", - "discount_received_account", "exchange_gain_loss_account", "unrealized_exchange_gain_loss_account", + "default_inter_company_account", "column_break0", "allow_account_creation_against_child_company", "default_payable_account", @@ -261,14 +260,14 @@ { "fieldname": "create_chart_of_accounts_based_on", "fieldtype": "Select", - "label": "Create Chart of Accounts Based on", + "label": "Create Chart Of Accounts Based On", "options": "\nStandard Template\nExisting Company" }, { "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", "fieldname": "chart_of_accounts", "fieldtype": "Select", - "label": "Chart of Accounts Template", + "label": "Chart Of Accounts Template", "no_copy": 1 }, { @@ -345,18 +344,6 @@ "label": "Write Off Account", "options": "Account" }, - { - "fieldname": "discount_allowed_account", - "fieldtype": "Link", - "label": "Discount Allowed Account", - "options": "Account" - }, - { - "fieldname": "discount_received_account", - "fieldtype": "Link", - "label": "Discount Received Account", - "options": "Account" - }, { "fieldname": "exchange_gain_loss_account", "fieldtype": "Link", @@ -740,6 +727,12 @@ "fieldtype": "Link", "label": "Default In Transit Warehouse", "options": "Warehouse" + }, + { + "fieldname": "default_inter_company_account", + "fieldtype": "Link", + "label": "Default Inter Company Account", + "options": "Account" } ], "icon": "fa fa-building", @@ -747,7 +740,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2020-08-06 00:38:08.311216", + "modified": "2020-11-26 18:20:48.184507", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -808,4 +801,4 @@ "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} +} \ No newline at end of file