diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index e262f73ebbe..ba6d5fa6c97 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -1333,15 +1333,9 @@
"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",
@@ -1349,6 +1343,7 @@
},
{
"depends_on": "eval:doc.is_internal_supplier",
+ "description": "Company which internal supplier represents",
"fetch_from": "supplier.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
@@ -1360,7 +1355,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-29 19:47:04.827315",
+ "modified": "2020-12-11 12:46:12.796378",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index d3a80e19ca8..6799fb986aa 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1954,6 +1954,7 @@
},
{
"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",
@@ -1961,6 +1962,7 @@
},
{
"depends_on": "eval:doc.is_internal_customer",
+ "description": "Company which internal customer represents",
"fetch_from": "customer.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
@@ -1973,7 +1975,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-29 18:48:15.012300",
+ "modified": "2020-12-11 12:48:31.769958",
"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 cbb0a7e1151..24ab21e9138 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1613,7 +1613,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if source_doc.get('update_stock'):
item_field_map.update({
'field_map': {
- source_document_warehouse_field: target_document_warehouse_field
+ source_document_warehouse_field: target_document_warehouse_field,
+ 'batch_no': 'batch_no',
+ 'serial_no': 'serial_no'
}
})
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 1e623a39fab..e096afc5046 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2110,10 +2110,11 @@ def create_internal_customer(customer_name, represents_company, allowed_to_inter
})
customer.insert()
+ customer_name = customer.name
else:
- customer = frappe.db.get_value("Customer", customer_name)
+ customer_name = frappe.db.get_value("Customer", customer_name)
- return customer
+ return customer_name
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
@@ -2130,10 +2131,11 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
})
supplier.insert()
+ supplier_name = supplier.name
else:
- supplier = frappe.db.exists("Supplier", supplier_name)
+ supplier_name = frappe.db.exists("Supplier", supplier_name)
- return supplier
+ return supplier_name
def add_taxes(doc):
doc.append('taxes', {
@@ -2142,4 +2144,4 @@ def add_taxes(doc):
"cost_center": "Main - TCP1",
"description": "Excise Duty",
"rate": 12
- })
\ No newline at end of file
+ })
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index dd8f452af20..c60c9ba4ffb 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -105,6 +105,8 @@ class AccountsController(TransactionBase):
else:
self.validate_deferred_start_and_end_date()
+ self.set_inter_company_account()
+
validate_regional(self)
validate_einvoice_fields(self)
@@ -921,6 +923,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)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index e05c70e41c1..dbf349569af 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -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:
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index e992d58e24e..6bf61e88ac8 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -92,16 +92,6 @@ class StockController(AccountsController):
sle = self.update_stock_ledger_entries(sle)
- gl_list.append(self.get_gl_dict({
- "account": warehouse_account[sle.warehouse]["account"],
- "against": item_row.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",
- "debit": flt(sle.stock_value_difference, precision),
- "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/ target_warehouse / source_warehouse
if item_row.get('target_warehouse'):
warehouse = item_row.get('target_warehouse')
@@ -109,6 +99,16 @@ class StockController(AccountsController):
else:
expense_account = item_row.expense_account
+ gl_list.append(self.get_gl_dict({
+ "account": warehouse_account[sle.warehouse]["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",
+ "debit": flt(sle.stock_value_difference, precision),
+ "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
+ }, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
+
gl_list.append(self.get_gl_dict({
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py
index dc38b25bed7..86f65285244 100644
--- a/erpnext/regional/india/e_invoice_utils.py
+++ b/erpnext/regional/india/e_invoice_utils.py
@@ -15,20 +15,27 @@ from Crypto.Util.Padding import pad, unpad
from frappe.model.document import Document
from frappe import _, get_module_path, scrub
from erpnext.regional.india.utils import get_gst_accounts
-from frappe.utils.data import get_datetime, cstr, cint, format_date, flt
from frappe.integrations.utils import make_post_request, make_get_request
+from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime
def validate_einvoice_fields(doc):
e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable")
if not doc.doctype in ['Sales Invoice', 'Purchase Invoice'] or not e_invoice_enabled: return
- if doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
+ if doc.docstatus == 0 and doc._action == 'save' and doc.irn:
+ frappe.throw(_("You cannot edit the invoice after generating IRN"), title=_("Edit Not Allowed"))
+ elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
frappe.throw(_("You must generate IRN before submitting the document."), title=_("Missing IRN"))
elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
- frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Not Allowed"))
+ frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Cancel Not Allowed"))
-def get_einv_credentials():
- return frappe.get_doc("E Invoice Settings")
+def get_einv_credentials(for_token=False):
+ creds = frappe.get_doc("E Invoice Settings")
+ if not for_token and (not creds.token_expiry or time_diff_in_seconds(now_datetime(), creds.token_expiry) > 5.0):
+ fetch_token()
+ creds.load_from_db()
+
+ return creds
def rsa_encrypt(msg, key):
if not (isinstance(msg, bytes) or isinstance(msg, bytearray)):
@@ -76,7 +83,7 @@ def get_header(creds):
@frappe.whitelist()
def fetch_token():
- einv_creds = get_einv_credentials()
+ einv_creds = get_einv_credentials(for_token=True)
endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth'
headers = { 'content-type': 'application/json' }
@@ -266,20 +273,26 @@ def get_doc_details(invoice):
))
def get_party_gstin_details(party_address):
- gstin, address_line1, address_line2, phone, email_id = frappe.db.get_value(
- "Address", party_address, ["gstin", "address_line1", "address_line2", "phone", "email_id"]
- )
+ address = frappe.get_all("Address", filters={"name": party_address}, fields=["*"])[0]
+
+ gstin = address.get('gstin')
gstin_details = get_gstin_details(gstin)
+ # legal_name = address.get('address_title')
legal_name = gstin_details.get('LegalName')
trade_name = gstin_details.get('TradeName')
- location = gstin_details.get('AddrLoc')
- state_code = gstin_details.get('StateCode')
- pincode = cint(gstin_details.get('AddrPncd'))
+ # location = address.get('city')
+ location = gstin_details.get('Loc')
+ state_code = address.get('gst_state_number')
+ pincode = cint(address.get('pincode'))
+ address_line1 = address.get('address_line1')
+ address_line2 = address.get('address_line2')
+ email_id = address.get('email_id')
+ phone = address.get('phone')
if state_code == 97:
pincode = 999999
return frappe._dict(dict(
- gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location,
+ gstin=gstin, legal_name=legal_name, location=location,
pincode=pincode, state_code=state_code, address_line1=address_line1,
address_line2=address_line2, email=email_id, phone=phone
))
@@ -338,8 +351,6 @@ def get_item_list(invoice):
e_inv_item = item_schema.format(item=item)
item_list.append(e_inv_item)
- print(e_inv_item)
-
return ", ".join(item_list)
def get_value_details(invoice):
@@ -404,10 +415,11 @@ def get_eway_bill_details(invoice):
vehicle_type=vehicle_type[invoice.gst_vehicle_type]
))
-def make_e_invoice(invoice):
+@frappe.whitelist()
+def make_e_invoice(doctype, name):
+ invoice = frappe.get_doc(doctype, name)
schema = read_json("einv_template")
- validations = read_json("einv_validation")
- validations = json.loads(validations)
+ validations = json.loads(read_json("einv_validation"))
trans_details = get_trans_details(invoice)
doc_details = get_doc_details(invoice)
@@ -451,13 +463,17 @@ def make_e_invoice(invoice):
)
e_invoice = json.loads(e_invoice)
- error_msgs = run_e_invoice_validations(validations, e_invoice, [])
+ error_msgs = validate_einvoice(validations, e_invoice, [])
if error_msgs:
- frappe.throw(_("{}").format("
".join(error_msgs)), title=_("E Invoice Validation"))
+ if len(error_msgs) > 1:
+ li = ["