summaryrefslogtreecommitdiff
path: root/addons/l10n_in/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/l10n_in/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/l10n_in/models')
-rw-r--r--addons/l10n_in/models/__init__.py12
-rw-r--r--addons/l10n_in/models/account.py55
-rw-r--r--addons/l10n_in/models/account_invoice.py129
-rw-r--r--addons/l10n_in/models/chart_template.py29
-rw-r--r--addons/l10n_in/models/port_code.py19
-rw-r--r--addons/l10n_in/models/product_template.py11
-rw-r--r--addons/l10n_in/models/res_config_settings.py10
-rw-r--r--addons/l10n_in/models/res_country_state.py10
-rw-r--r--addons/l10n_in/models/res_partner.py46
-rw-r--r--addons/l10n_in/models/uom_uom.py11
10 files changed, 332 insertions, 0 deletions
diff --git a/addons/l10n_in/models/__init__.py b/addons/l10n_in/models/__init__.py
new file mode 100644
index 00000000..2f479b39
--- /dev/null
+++ b/addons/l10n_in/models/__init__.py
@@ -0,0 +1,12 @@
+# -*- coding:utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import account
+from . import account_invoice
+from . import chart_template
+from . import product_template
+from . import port_code
+from . import res_config_settings
+from . import res_country_state
+from . import res_partner
+from . import uom_uom
diff --git a/addons/l10n_in/models/account.py b/addons/l10n_in/models/account.py
new file mode 100644
index 00000000..bdc6e36d
--- /dev/null
+++ b/addons/l10n_in/models/account.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+
+class AccountJournal(models.Model):
+ _inherit = "account.journal"
+
+ # Use for filter import and export type.
+ l10n_in_gstin_partner_id = fields.Many2one('res.partner', string="GSTIN Unit", ondelete="restrict", help="GSTIN related to this journal. If empty then consider as company GSTIN.")
+
+ def name_get(self):
+ """
+ Add GSTIN number in name as suffix so user can easily find the right journal.
+ Used super to ensure nothing is missed.
+ """
+ result = super().name_get()
+ result_dict = dict(result)
+ indian_journals = self.filtered(lambda j: j.company_id.country_id.code == 'IN' and
+ j.l10n_in_gstin_partner_id and j.l10n_in_gstin_partner_id.vat)
+ for journal in indian_journals:
+ name = result_dict[journal.id]
+ name += "- %s" % (journal.l10n_in_gstin_partner_id.vat)
+ result_dict[journal.id] = name
+ return list(result_dict.items())
+
+
+class AccountMoveLine(models.Model):
+ _inherit = "account.move.line"
+
+ @api.depends('move_id.line_ids', 'move_id.line_ids.tax_line_id', 'move_id.line_ids.debit', 'move_id.line_ids.credit')
+ def _compute_tax_base_amount(self):
+ aml = self.filtered(lambda l: l.company_id.country_id.code == 'IN' and l.tax_line_id and l.product_id)
+ for move_line in aml:
+ base_lines = move_line.move_id.line_ids.filtered(lambda line: move_line.tax_line_id in line.tax_ids and move_line.product_id == line.product_id)
+ move_line.tax_base_amount = abs(sum(base_lines.mapped('balance')))
+ remaining_aml = self - aml
+ if remaining_aml:
+ return super(AccountMoveLine, remaining_aml)._compute_tax_base_amount()
+
+
+class AccountTax(models.Model):
+ _inherit = 'account.tax'
+
+ l10n_in_reverse_charge = fields.Boolean("Reverse charge", help="Tick this if this tax is reverse charge. Only for Indian accounting")
+
+ def get_grouping_key(self, invoice_tax_val):
+ """ Returns a string that will be used to group account.invoice.tax sharing the same properties"""
+ key = super(AccountTax, self).get_grouping_key(invoice_tax_val)
+ if self.company_id.country_id.code == 'IN':
+ key += "-%s-%s"% (invoice_tax_val.get('l10n_in_product_id', False),
+ invoice_tax_val.get('l10n_in_uom_id', False))
+ return key
diff --git a/addons/l10n_in/models/account_invoice.py b/addons/l10n_in/models/account_invoice.py
new file mode 100644
index 00000000..d755cebd
--- /dev/null
+++ b/addons/l10n_in/models/account_invoice.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+
+class AccountMove(models.Model):
+ _inherit = "account.move"
+
+ @api.depends('amount_total')
+ def _compute_amount_total_words(self):
+ for invoice in self:
+ invoice.amount_total_words = invoice.currency_id.amount_to_text(invoice.amount_total)
+
+ amount_total_words = fields.Char("Total (In Words)", compute="_compute_amount_total_words")
+ l10n_in_gst_treatment = fields.Selection([
+ ('regular', 'Registered Business - Regular'),
+ ('composition', 'Registered Business - Composition'),
+ ('unregistered', 'Unregistered Business'),
+ ('consumer', 'Consumer'),
+ ('overseas', 'Overseas'),
+ ('special_economic_zone', 'Special Economic Zone'),
+ ('deemed_export', 'Deemed Export')
+ ], string="GST Treatment", readonly=True, states={'draft': [('readonly', False)]})
+ l10n_in_state_id = fields.Many2one('res.country.state', string="Location of supply")
+ l10n_in_company_country_code = fields.Char(related='company_id.country_id.code', string="Country code")
+ l10n_in_gstin = fields.Char(string="GSTIN")
+ # For Export invoice this data is need in GSTR report
+ l10n_in_shipping_bill_number = fields.Char('Shipping bill number', readonly=True, states={'draft': [('readonly', False)]})
+ l10n_in_shipping_bill_date = fields.Date('Shipping bill date', readonly=True, states={'draft': [('readonly', False)]})
+ l10n_in_shipping_port_code_id = fields.Many2one('l10n_in.port.code', 'Port code', states={'draft': [('readonly', False)]})
+ l10n_in_reseller_partner_id = fields.Many2one('res.partner', 'Reseller', domain=[('vat', '!=', False)], help="Only Registered Reseller", readonly=True, states={'draft': [('readonly', False)]})
+
+ @api.onchange('partner_id')
+ def _onchange_partner_id(self):
+ """Use journal type to define document type because not miss state in any entry including POS entry"""
+ if self.l10n_in_company_country_code == 'IN':
+ self.l10n_in_gst_treatment = self.partner_id.l10n_in_gst_treatment
+ return super()._onchange_partner_id()
+
+ @api.model
+ def _l10n_in_get_indian_state(self, partner):
+ """In tax return filing, If customer is not Indian in that case place of supply is must set to Other Territory.
+ So we set Other Territory in l10n_in_state_id when customer(partner) is not Indian
+ Also we raise if state is not set in Indian customer.
+ State is big role under GST because tax type is depend on.for more information check this https://www.cbic.gov.in/resources//htdocs-cbec/gst/Integrated%20goods%20&%20Services.pdf"""
+ if partner.country_id and partner.country_id.code == 'IN' and not partner.state_id:
+ raise ValidationError(_("State is missing from address in '%s'. First set state after post this invoice again.", partner.name))
+ elif partner.country_id and partner.country_id.code != 'IN':
+ return self.env.ref('l10n_in.state_in_ot')
+ return partner.state_id
+
+
+ @api.model
+ def _get_tax_grouping_key_from_tax_line(self, tax_line):
+ # OVERRIDE to group taxes also by product.
+ res = super()._get_tax_grouping_key_from_tax_line(tax_line)
+ if tax_line.move_id.journal_id.company_id.country_id.code == 'IN':
+ res['product_id'] = tax_line.product_id.id
+ res['product_uom_id'] = tax_line.product_uom_id
+ return res
+
+ @api.model
+ def _get_tax_grouping_key_from_base_line(self, base_line, tax_vals):
+ # OVERRIDE to group taxes also by product.
+ res = super()._get_tax_grouping_key_from_base_line(base_line, tax_vals)
+ if base_line.move_id.journal_id.company_id.country_id.code == 'IN':
+ res['product_id'] = base_line.product_id.id
+ res['product_uom_id'] = base_line.product_uom_id
+ return res
+
+ @api.model
+ def _get_tax_key_for_group_add_base(self, line):
+ # DEPRECATED: TO BE REMOVED IN MASTER
+ tax_key = super(AccountMove, self)._get_tax_key_for_group_add_base(line)
+
+ tax_key += [
+ line.product_id.id,
+ line.product_uom_id,
+ ]
+ return tax_key
+
+ def _l10n_in_get_shipping_partner(self):
+ """Overwrite in sale"""
+ self.ensure_one()
+ return self.partner_id
+
+ @api.model
+ def _l10n_in_get_shipping_partner_gstin(self, shipping_partner):
+ """Overwrite in sale"""
+ return shipping_partner.vat
+
+ def _post(self, soft=True):
+ """Use journal type to define document type because not miss state in any entry including POS entry"""
+ posted = super()._post(soft)
+ gst_treatment_name_mapping = {k: v for k, v in
+ self._fields['l10n_in_gst_treatment']._description_selection(self.env)}
+ for move in posted.filtered(lambda m: m.l10n_in_company_country_code == 'IN'):
+ """Check state is set in company/sub-unit"""
+ company_unit_partner = move.journal_id.l10n_in_gstin_partner_id or move.journal_id.company_id
+ if not company_unit_partner.state_id:
+ raise ValidationError(_(
+ "State is missing from your company/unit %(company_name)s (%(company_id)s).\nFirst set state in your company/unit.",
+ company_name=company_unit_partner.name,
+ company_id=company_unit_partner.id
+ ))
+ elif self.journal_id.type == 'purchase':
+ move.l10n_in_state_id = company_unit_partner.state_id
+
+ shipping_partner = move._l10n_in_get_shipping_partner()
+ # In case of shipping address does not have GSTN then also check customer(partner_id) GSTN
+ # This happens when Bill-to Ship-to transaction where shipping(Ship-to) address is unregistered and customer(Bill-to) is registred.
+ move.l10n_in_gstin = move._l10n_in_get_shipping_partner_gstin(shipping_partner) or move.partner_id.vat
+ if not move.l10n_in_gstin and move.l10n_in_gst_treatment in ['regular', 'composition', 'special_economic_zone', 'deemed_export']:
+ raise ValidationError(_(
+ "Partner %(partner_name)s (%(partner_id)s) GSTIN is required under GST Treatment %(name)s",
+ partner_name=shipping_partner.name,
+ partner_id=shipping_partner.id,
+ name=gst_treatment_name_mapping.get(move.l10n_in_gst_treatment)
+ ))
+ if self.journal_id.type == 'sale':
+ move.l10n_in_state_id = self._l10n_in_get_indian_state(shipping_partner)
+ if not move.l10n_in_state_id:
+ move.l10n_in_state_id = self._l10n_in_get_indian_state(move.partner_id)
+ #still state is not set then assumed that transaction is local like PoS so set state of company unit
+ if not move.l10n_in_state_id:
+ move.l10n_in_state_id = company_unit_partner.state_id
+ return posted
diff --git a/addons/l10n_in/models/chart_template.py b/addons/l10n_in/models/chart_template.py
new file mode 100644
index 00000000..cb18bb75
--- /dev/null
+++ b/addons/l10n_in/models/chart_template.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+
+
+class AccountChartTemplate(models.Model):
+ _inherit = 'account.chart.template'
+
+ def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
+ res = super(AccountChartTemplate, self)._prepare_all_journals(acc_template_ref, company, journals_dict=journals_dict)
+ if self == self.env.ref('l10n_in.indian_chart_template_standard'):
+ for journal in res:
+ if journal.get('type') in ('sale','purchase'):
+ journal['l10n_in_gstin_partner_id'] = company.partner_id.id
+ if journal['code'] == 'INV':
+ journal['name'] = _('Tax Invoices')
+ return res
+
+class AccountTaxTemplate(models.Model):
+ _inherit = 'account.tax.template'
+
+ l10n_in_reverse_charge = fields.Boolean("Reverse charge", help="Tick this if this tax is reverse charge. Only for Indian accounting")
+
+ def _get_tax_vals(self, company, tax_template_to_tax):
+ val = super(AccountTaxTemplate, self)._get_tax_vals(company, tax_template_to_tax)
+ if self.tax_group_id:
+ val['l10n_in_reverse_charge'] = self.l10n_in_reverse_charge
+ return val
diff --git a/addons/l10n_in/models/port_code.py b/addons/l10n_in/models/port_code.py
new file mode 100644
index 00000000..dfd54f34
--- /dev/null
+++ b/addons/l10n_in/models/port_code.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class L10nInPortCode(models.Model):
+ """Port code must be mentioned in export and import of goods under GST."""
+ _name = 'l10n_in.port.code'
+ _description = "Indian port code"
+ _rec_name = 'code'
+
+ code = fields.Char(string="Port Code", required=True)
+ name = fields.Char(string="Port", required=True)
+ state_id = fields.Many2one('res.country.state', string="State")
+
+ _sql_constraints = [
+ ('code_uniq', 'unique (code)', 'The Port Code must be unique!')
+ ]
diff --git a/addons/l10n_in/models/product_template.py b/addons/l10n_in/models/product_template.py
new file mode 100644
index 00000000..6250fac2
--- /dev/null
+++ b/addons/l10n_in/models/product_template.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, fields
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ l10n_in_hsn_code = fields.Char(string="HSN/SAC Code", help="Harmonized System Nomenclature/Services Accounting Code")
+ l10n_in_hsn_description = fields.Char(string="HSN/SAC Description", help="HSN/SAC description is required if HSN/SAC code is not provided.")
diff --git a/addons/l10n_in/models/res_config_settings.py b/addons/l10n_in/models/res_config_settings.py
new file mode 100644
index 00000000..11a58d95
--- /dev/null
+++ b/addons/l10n_in/models/res_config_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = 'res.config.settings'
+
+ group_l10n_in_reseller = fields.Boolean(implied_group='l10n_in.group_l10n_in_reseller', string="Manage Reseller(E-Commerce)")
diff --git a/addons/l10n_in/models/res_country_state.py b/addons/l10n_in/models/res_country_state.py
new file mode 100644
index 00000000..6cb495a7
--- /dev/null
+++ b/addons/l10n_in/models/res_country_state.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class CountryState(models.Model):
+ _inherit = 'res.country.state'
+
+ l10n_in_tin = fields.Char('TIN Number', size=2, help="TIN number-first two digits")
diff --git a/addons/l10n_in/models/res_partner.py b/addons/l10n_in/models/res_partner.py
new file mode 100644
index 00000000..f14e3953
--- /dev/null
+++ b/addons/l10n_in/models/res_partner.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+
+
+class ResPartner(models.Model):
+ _inherit = 'res.partner'
+
+ l10n_in_gst_treatment = fields.Selection([
+ ('regular', 'Registered Business - Regular'),
+ ('composition', 'Registered Business - Composition'),
+ ('unregistered', 'Unregistered Business'),
+ ('consumer', 'Consumer'),
+ ('overseas', 'Overseas'),
+ ('special_economic_zone', 'Special Economic Zone'),
+ ('deemed_export', 'Deemed Export'),
+ ], string="GST Treatment")
+
+ @api.onchange('company_type')
+ def onchange_company_type(self):
+ res = super().onchange_company_type()
+ if self.country_id and self.country_id.code == 'IN':
+ self.l10n_in_gst_treatment = (self.company_type == 'company') and 'regular' or 'consumer'
+ return res
+
+ @api.onchange('country_id')
+ def _onchange_country_id(self):
+ res = super()._onchange_country_id()
+ if self.country_id and self.country_id.code != 'IN':
+ self.l10n_in_gst_treatment = 'overseas'
+ elif self.country_id and self.country_id.code == 'IN':
+ self.l10n_in_gst_treatment = (self.company_type == 'company') and 'regular' or 'consumer'
+ return res
+
+ @api.onchange('vat')
+ def onchange_vat(self):
+ if self.vat and self.check_vat_in(self.vat):
+ state_id = self.env['res.country.state'].search([('l10n_in_tin', '=', self.vat[:2])], limit=1)
+ if state_id:
+ self.state_id = state_id
+
+ @api.model
+ def _commercial_fields(self):
+ res = super()._commercial_fields()
+ return res + ['l10n_in_gst_treatment']
diff --git a/addons/l10n_in/models/uom_uom.py b/addons/l10n_in/models/uom_uom.py
new file mode 100644
index 00000000..b9b3fcc5
--- /dev/null
+++ b/addons/l10n_in/models/uom_uom.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class UoM(models.Model):
+ _inherit = "uom.uom"
+
+ # As per GST Rules you need to Specify UQC given by GST.
+ l10n_in_code = fields.Char("Indian GST UQC", help="Unique Quantity Code (UQC) under GST")