fix: Accounting for internal transfer invoices within same company (#24021)
* fix: Accounting for internal transfer invoices within same company * fix: warehouse fetching * fix: Linting issues * fix: GL entry fixes and validation for intercompany account * fix: Account naming changes and other fixes * fix: Add test for internal transfer * fix: Test Case * fix: Add description for fields * fix: Commonfied code * fix: Map warehouse and serial no
This commit is contained in:
parent
f8d6726990
commit
f17ea2ccab
@ -15,6 +15,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
return (doc.qty<=doc.received_qty) ? "green" : "orange";
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.set_query("unrealized_profit_loss_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
is_group: 0,
|
||||
root_type: "Liability",
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
onload: function() {
|
||||
this._super();
|
||||
|
@ -1,6 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
@ -127,6 +126,7 @@
|
||||
"write_off_cost_center",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"adjust_advance_taxes",
|
||||
"get_advances",
|
||||
"advances",
|
||||
"payment_schedule_section",
|
||||
@ -152,9 +152,11 @@
|
||||
"is_opening",
|
||||
"against_expense_account",
|
||||
"column_break_63",
|
||||
"unrealized_profit_loss_account",
|
||||
"status",
|
||||
"inter_company_invoice_reference",
|
||||
"is_internal_supplier",
|
||||
"represents_company",
|
||||
"remarks",
|
||||
"subscription_section",
|
||||
"from_date",
|
||||
@ -1223,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
|
||||
},
|
||||
{
|
||||
@ -1330,13 +1332,37 @@
|
||||
"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",
|
||||
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||
"fieldname": "unrealized_profit_loss_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Unrealized Profit / Loss Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_internal_supplier",
|
||||
"description": "Company which internal supplier represents",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2020-10-30 13:57:18.266978",
|
||||
"modified": "2020-12-11 12:46:12.796378",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -206,8 +206,8 @@ class PurchaseInvoice(BuyingController):
|
||||
["Purchase Receipt", "purchase_receipt", "pr_detail"]
|
||||
])
|
||||
|
||||
def validate_warehouse(self):
|
||||
if self.update_stock:
|
||||
def validate_warehouse(self, for_validate=True):
|
||||
if self.update_stock and for_validate:
|
||||
for d in self.get('items'):
|
||||
if not d.warehouse:
|
||||
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
|
||||
@ -233,7 +233,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if self.update_stock:
|
||||
self.validate_item_code()
|
||||
self.validate_warehouse()
|
||||
self.validate_warehouse(for_validate)
|
||||
if auto_accounting_for_stock:
|
||||
warehouse_account = get_warehouse_account_map(self.company)
|
||||
|
||||
@ -449,6 +449,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)
|
||||
|
||||
@ -457,7 +458,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_payment_gl_entries(gl_entries)
|
||||
self.make_write_off_gl_entry(gl_entries)
|
||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def check_asset_cwip_enabled(self):
|
||||
@ -474,31 +474,30 @@ 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)
|
||||
|
||||
@ -526,7 +525,6 @@ class PurchaseInvoice(BuyingController):
|
||||
item, voucher_wise_stock_value, account_currency)
|
||||
|
||||
if item.from_warehouse:
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": warehouse_account[item.warehouse]['account'],
|
||||
"against": warehouse_account[item.from_warehouse]["account"],
|
||||
@ -546,16 +544,18 @@ class PurchaseInvoice(BuyingController):
|
||||
"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)
|
||||
)
|
||||
# Do not book expense for transfer within same company transfer
|
||||
if not self.is_internal_transfer():
|
||||
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(
|
||||
@ -832,7 +832,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)
|
||||
@ -876,8 +877,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.unrealized_profit_loss_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.unrealized_profit_loss_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
|
||||
@ -1095,7 +1107,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"
|
||||
|
@ -4,23 +4,25 @@
|
||||
// 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') {
|
||||
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
|
||||
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
|
||||
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
||||
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
||||
if(cint(doc.on_hold) && !doc.release_date) {
|
||||
return [__("On Hold"), "darkgrey"];
|
||||
} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
|
||||
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
|
||||
return [__("Temporarily on Hold"), "darkgrey"];
|
||||
} else if(frappe.datetime.get_diff(doc.due_date) < 0) {
|
||||
} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
|
||||
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
|
||||
} else {
|
||||
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
|
||||
}
|
||||
} else if(cint(doc.is_return)) {
|
||||
} else if (cint(doc.is_return)) {
|
||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||
} else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
|
||||
} 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"];
|
||||
}
|
||||
}
|
||||
|
@ -580,6 +580,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("unrealized_profit_loss_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',
|
||||
|
@ -1,6 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-24 19:29:05",
|
||||
@ -158,6 +157,7 @@
|
||||
"more_information",
|
||||
"inter_company_invoice_reference",
|
||||
"is_internal_customer",
|
||||
"represents_company",
|
||||
"customer_group",
|
||||
"campaign",
|
||||
"is_discounted",
|
||||
@ -171,6 +171,7 @@
|
||||
"c_form_applicable",
|
||||
"c_form_no",
|
||||
"column_break8",
|
||||
"unrealized_profit_loss_account",
|
||||
"remarks",
|
||||
"sales_team_section_break",
|
||||
"sales_partner",
|
||||
@ -1655,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
|
||||
},
|
||||
@ -1950,13 +1951,31 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Company Tax ID",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_internal_customer",
|
||||
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||
"fieldname": "unrealized_profit_loss_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Unrealized Profit / Loss Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_internal_customer",
|
||||
"description": "Company which internal customer represents",
|
||||
"fetch_from": "customer.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-30 13:57:45.086303",
|
||||
"modified": "2020-12-11 12:48:31.769958",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -758,6 +758,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)
|
||||
|
||||
@ -777,7 +778,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"))
|
||||
@ -816,6 +817,18 @@ 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.unrealized_profit_loss_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.unrealized_profit_loss_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"):
|
||||
@ -838,22 +851,24 @@ class SalesInvoice(SellingController):
|
||||
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)
|
||||
# Do not book income for transfer within same company
|
||||
if not self.is_internal_transfer():
|
||||
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 \
|
||||
@ -1265,7 +1280,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"
|
||||
@ -1530,9 +1547,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)
|
||||
@ -1559,6 +1580,26 @@ 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,
|
||||
'batch_no': 'batch_no',
|
||||
'serial_no': 'serial_no'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
doclist = get_mapped_doc(doctype, source_name, {
|
||||
doctype: {
|
||||
"doctype": target_doctype,
|
||||
@ -1567,15 +1608,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)
|
||||
|
||||
|
@ -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];
|
||||
},
|
||||
|
@ -1573,7 +1573,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
||||
|
||||
|
||||
def test_sales_invoice_with_project_link(self):
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
|
||||
@ -1607,9 +1607,9 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
|
||||
order by account asc""", sales_invoice.name, as_dict=1)
|
||||
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertEqual(expected_values[gle.account]["project"], gle.project)
|
||||
|
||||
@ -1781,6 +1781,60 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(target_doc.company, "_Test Company 1")
|
||||
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
||||
|
||||
def test_internal_transfer_gl_entry(self):
|
||||
## Create internal transfer account
|
||||
account = create_account(account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
|
||||
|
||||
frappe.db.set_value('Company', '_Test Company with perpetual inventory',
|
||||
'unrealized_profit_loss_account', account)
|
||||
|
||||
customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory")
|
||||
|
||||
create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory")
|
||||
|
||||
si = create_sales_invoice(
|
||||
company = "_Test Company with perpetual inventory",
|
||||
customer = customer,
|
||||
debit_to = "Debtors - TCP1",
|
||||
warehouse = "Stores - TCP1",
|
||||
income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1",
|
||||
cost_center = "Main - TCP1",
|
||||
currency = "INR",
|
||||
do_not_save = 1
|
||||
)
|
||||
|
||||
si.selling_price_list = "_Test Price List Rest of the World"
|
||||
si.update_stock = 1
|
||||
si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
||||
add_taxes(si)
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
||||
target_doc.company = '_Test Company with perpetual inventory'
|
||||
target_doc.items[0].warehouse = 'Finished Goods - TCP1'
|
||||
add_taxes(target_doc)
|
||||
target_doc.save()
|
||||
target_doc.submit()
|
||||
|
||||
si_gl_entries = [
|
||||
["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
|
||||
["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
|
||||
|
||||
pi_gl_entries = [
|
||||
["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
|
||||
["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
||||
|
||||
def test_eway_bill_json(self):
|
||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||
address = frappe.get_doc({
|
||||
@ -2039,4 +2093,57 @@ def get_taxes_and_charges():
|
||||
"parentfield": "taxes",
|
||||
"rate": 2,
|
||||
"row_id": 1
|
||||
}]
|
||||
}]
|
||||
|
||||
def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
customer = frappe.get_doc({
|
||||
"customer_group": "_Test Customer Group",
|
||||
"customer_name": customer_name,
|
||||
"customer_type": "Individual",
|
||||
"doctype": "Customer",
|
||||
"territory": "_Test Territory",
|
||||
"is_internal_customer": 1,
|
||||
"represents_company": represents_company
|
||||
})
|
||||
|
||||
customer.append("companies", {
|
||||
"company": allowed_to_interact_with
|
||||
})
|
||||
|
||||
customer.insert()
|
||||
customer_name = customer.name
|
||||
else:
|
||||
customer_name = frappe.db.get_value("Customer", customer_name)
|
||||
|
||||
return customer_name
|
||||
|
||||
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
|
||||
if not frappe.db.exists("Supplier", supplier_name):
|
||||
supplier = frappe.get_doc({
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
"supplier_name": supplier_name,
|
||||
"doctype": "Supplier",
|
||||
"is_internal_supplier": 1,
|
||||
"represents_company": represents_company
|
||||
})
|
||||
|
||||
supplier.append("companies", {
|
||||
"company": allowed_to_interact_with
|
||||
})
|
||||
|
||||
supplier.insert()
|
||||
supplier_name = supplier.name
|
||||
else:
|
||||
supplier_name = frappe.db.exists("Supplier", supplier_name)
|
||||
|
||||
return supplier_name
|
||||
|
||||
def add_taxes(doc):
|
||||
doc.append('taxes', {
|
||||
'account_head': '_Test Account Excise Duty - TCP1',
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "Main - TCP1",
|
||||
"description": "Excise Duty",
|
||||
"rate": 12
|
||||
})
|
@ -49,6 +49,12 @@ class Supplier(TransactionBase):
|
||||
msgprint(_("Series is mandatory"), raise_exception=1)
|
||||
|
||||
validate_party_accounts(self)
|
||||
self.validate_internal_supplier()
|
||||
|
||||
def validate_internal_supplier(self):
|
||||
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
|
||||
frappe.throw(_("Internal Supplier for company {0} already exists").format(
|
||||
frappe.bold(self.represents_company)))
|
||||
|
||||
def on_trash(self):
|
||||
delete_contact_and_address('Supplier', self.name)
|
||||
|
@ -107,6 +107,8 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
self.validate_deferred_start_and_end_date()
|
||||
|
||||
self.set_inter_company_account()
|
||||
|
||||
validate_regional(self)
|
||||
if self.doctype != 'Material Request':
|
||||
apply_pricing_rule_on_transaction(self)
|
||||
@ -932,6 +934,38 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
|
||||
|
||||
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.unrealized_profit_loss_account:
|
||||
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
|
||||
|
||||
if not unrealized_profit_loss_account:
|
||||
msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
|
||||
frappe.bold(self.company))
|
||||
frappe.throw(msg)
|
||||
|
||||
self.unrealized_profit_loss_account = unrealized_profit_loss_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.doctype == 'Sales Invoice':
|
||||
internal_party_field = 'is_internal_customer'
|
||||
else:
|
||||
internal_party_field = 'is_internal_supplier'
|
||||
|
||||
if self.get(internal_party_field) and (self.represents_company == self.company):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tax_rate(account_head):
|
||||
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
|
||||
|
@ -42,6 +42,7 @@ class BuyingController(StockController):
|
||||
self.validate_items()
|
||||
self.set_qty_as_per_stock_uom()
|
||||
self.validate_stock_or_nonstock_items()
|
||||
self.update_tax_category_for_internal_transfer()
|
||||
self.validate_warehouse()
|
||||
self.validate_from_warehouse()
|
||||
self.set_supplier_address()
|
||||
@ -94,13 +95,23 @@ class BuyingController(StockController):
|
||||
|
||||
def validate_stock_or_nonstock_items(self):
|
||||
if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items():
|
||||
tax_for_valuation = [d for d in self.get("taxes")
|
||||
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
|
||||
self.update_tax_category(msg)
|
||||
|
||||
def update_tax_category_for_internal_transfer(self):
|
||||
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
|
||||
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
|
||||
self.update_tax_category(msg)
|
||||
|
||||
def update_tax_category(self, msg):
|
||||
tax_for_valuation = [d for d in self.get("taxes")
|
||||
if d.category in ["Valuation", "Valuation and Total"]]
|
||||
|
||||
if tax_for_valuation:
|
||||
for d in tax_for_valuation:
|
||||
d.category = 'Total'
|
||||
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
|
||||
if tax_for_valuation:
|
||||
for d in tax_for_valuation:
|
||||
d.category = 'Total'
|
||||
|
||||
msgprint(msg)
|
||||
|
||||
def validate_asset_return(self):
|
||||
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
|
||||
|
@ -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)
|
||||
|
||||
@ -92,9 +92,16 @@ class StockController(AccountsController):
|
||||
|
||||
sle = self.update_stock_ledger_entries(sle)
|
||||
|
||||
# expense account/ target_warehouse / source_warehouse
|
||||
if item_row.get('target_warehouse'):
|
||||
warehouse = item_row.get('target_warehouse')
|
||||
expense_account = warehouse_account[warehouse]["account"]
|
||||
else:
|
||||
expense_account = item_row.expense_account
|
||||
|
||||
gl_list.append(self.get_gl_dict({
|
||||
"account": warehouse_account[sle.warehouse]["account"],
|
||||
"against": item_row.expense_account,
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get('project'),
|
||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||
@ -102,9 +109,8 @@ 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
|
||||
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'),
|
||||
|
@ -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'])
|
||||
|
@ -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"]);
|
||||
|
||||
|
@ -58,6 +58,7 @@ class Customer(TransactionBase):
|
||||
self.set_loyalty_program()
|
||||
self.check_customer_group_change()
|
||||
self.validate_default_bank_account()
|
||||
self.validate_internal_customer()
|
||||
|
||||
# set loyalty program tier
|
||||
if frappe.db.exists('Customer', self.name):
|
||||
@ -82,6 +83,11 @@ class Customer(TransactionBase):
|
||||
if not is_company_account:
|
||||
frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account)))
|
||||
|
||||
def validate_internal_customer(self):
|
||||
if self.is_internal_customer and frappe.db.get_value('Customer', {"represents_company": self.represents_company}, "name"):
|
||||
frappe.throw(_("Internal Customer for company {0} already exists").format(
|
||||
frappe.bold(self.represents_company)))
|
||||
|
||||
def on_update(self):
|
||||
self.validate_name_with_customer_group()
|
||||
self.create_primary_contact()
|
||||
@ -398,7 +404,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
|
||||
# form a list of emails and names to show to the user
|
||||
credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
|
||||
if not credit_controller_users_formatted:
|
||||
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
|
||||
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.").format(customer))
|
||||
|
||||
message = """Please contact any of the following users to extend the credit limits for {0}:
|
||||
<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted))
|
||||
|
@ -274,7 +274,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"}],
|
||||
["unrealized_profit_loss_account", {"root_type": "Liability"}]
|
||||
], function(i, v) {
|
||||
erpnext.company.set_custom_query(frm, v);
|
||||
});
|
||||
|
@ -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",
|
||||
"unrealized_profit_loss_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": "unrealized_profit_loss_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Unrealized Profit / Loss 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-12-03 12:27:27.085094",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
@ -808,4 +801,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user