diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/l10n_in/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/l10n_in/models')
| -rw-r--r-- | addons/l10n_in/models/__init__.py | 12 | ||||
| -rw-r--r-- | addons/l10n_in/models/account.py | 55 | ||||
| -rw-r--r-- | addons/l10n_in/models/account_invoice.py | 129 | ||||
| -rw-r--r-- | addons/l10n_in/models/chart_template.py | 29 | ||||
| -rw-r--r-- | addons/l10n_in/models/port_code.py | 19 | ||||
| -rw-r--r-- | addons/l10n_in/models/product_template.py | 11 | ||||
| -rw-r--r-- | addons/l10n_in/models/res_config_settings.py | 10 | ||||
| -rw-r--r-- | addons/l10n_in/models/res_country_state.py | 10 | ||||
| -rw-r--r-- | addons/l10n_in/models/res_partner.py | 46 | ||||
| -rw-r--r-- | addons/l10n_in/models/uom_uom.py | 11 |
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") |
