oca-financial/odoo-bringout-oca-account-fiscal-rule-account_avatax_oca/account_avatax_oca/models/avalara_salestax.py
2025-08-29 15:43:04 +02:00

328 lines
11 KiB
Python

import logging
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from .avatax_rest_api import AvaTaxRESTService
_logger = logging.getLogger(__name__)
class ExemptionCode(models.Model):
_name = "exemption.code"
_description = "Exemption Code"
name = fields.Char(required=True)
code = fields.Char()
@api.depends("name", "code")
def name_get(self):
def name(r):
return r.code and "({}) {}".format(r.code, r.name) or r.name
return [(r.id, name(r)) for r in self]
class AvalaraSalestax(models.Model):
_name = "avalara.salestax"
_description = "AvaTax Configuration"
_rec_name = "account_number"
@api.model
def _get_avatax_supported_countries(self):
"""Returns the countries supported by AvaTax Address Validation Service."""
return self.env["res.country"].search([("code", "in", ["US", "CA"])])
account_number = fields.Char(
name="Account ID", required=True, help="Account Number provided by AvaTax"
)
license_key = fields.Char(required=True, help="License Key provided by AvaTax")
service_url = fields.Selection(
[
("https://rest.avatax.com/api/v2", "Production (REST API)"),
("https://sandbox-rest.avatax.com/api/v2", "Sandbox (REST API)"),
],
string="Service URL",
default="https://rest.avatax.com/api/v2",
help="The url to connect with",
)
request_timeout = fields.Integer(
default=300,
help="Defines AvaTax request time out length"
", AvaTax best practices prescribes default setting of 300 seconds",
)
company_code = fields.Char(
default="DEFAULT",
required=True,
help="The company code as defined in the Admin Console of AvaTax",
)
logging = fields.Boolean(
"Log API Requests",
help="Enables detailed AvaTax transaction logging within application",
)
result_in_uppercase = fields.Boolean(
"Return validation results in upper case",
help="The address validation results are returned in in upper case",
)
disable_address_validation = fields.Boolean(
help="Disables the ability to perform address validation"
)
validation_on_save = fields.Boolean(
"Automatic Address Validation",
help="Automatically validates addresses when they are created or modified",
)
force_address_validation = fields.Boolean(
"Require Validated Addresses",
help="Only compute taxes if addresses were validated by the Avatax service",
)
auto_generate_customer_code = fields.Boolean(
"Automatically generate missing customer code",
default=True,
help="This will generate customer code for the customer used in the "
"transaction, if it doesn't have one already. "
"Each code is unique per customer. "
"When this is disabled, you will have to manually go to each customer "
"and manually generate their customer code. "
"This is required for Avatax and is only generated one time.",
)
disable_tax_calculation = fields.Boolean(
"Disable AvaTax Calculation",
help="No tax calculation requests will be sent to the AvaTax web service.",
)
# TODO: Control - Disable Document Recording
# In order for this connector to be used in conjunction
# with other integrations to AvaTax, the user must be able to control which connector
# is used for recording documents to AvaTax.
# From a technical standpoint, simply use DocType: 'SalesOrder' on all calls
# and suppress any non-getTax calls (i.e. cancelTax, postTax).
disable_tax_reporting = fields.Boolean(
"Disable Document Recording/Commiting",
help="No transactions will be recorded (commited) to the Avatax service.",
)
country_ids = fields.Many2many(
"res.country",
"avalara_salestax_country_rel",
"avalara_salestax_id",
"country_id",
"Countries",
default=_get_avatax_supported_countries,
help="Countries where address validation will be used",
)
active = fields.Boolean(
default=True,
help="Uncheck the active field to hide the record",
)
company_id = fields.Many2one(
"res.company",
"Company",
required=True,
default=lambda self: self.env.company,
help="Company which has subscribed to the AvaTax service",
)
company_partner_id = fields.Many2one(
string="Company Address",
related="company_id.partner_id",
)
upc_enable = fields.Boolean(
"Enable UPC Taxability",
help="Allows ean13 to be reported in place of Item Reference"
" as upc identifier.",
)
invoice_calculate_tax = fields.Boolean(
"Auto Calculate Tax on Invoice Save",
help="Automatically triggers API to calculate tax If changes made on"
"Invoice's warehouse_id, tax_on_shipping_address, "
"Invoice line's price_unit, discount, quantity",
)
use_so_partner_id = fields.Boolean(
string="Use Sale Customer Code on Invoice",
help="Use Sales Order's Customer field to determine Taxable "
"Status on the Customer Invoice. If no Sales Order exists, "
"Customer field on the invoice form view will be used instead",
)
hide_exemption = fields.Boolean(
"Hide Exemption & Tax Based on shipping address",
default=False,
help="Uncheck the this field to show exemption fields on SO/Invoice form view. "
"Also, it will show Tax based on shipping address button",
)
# TODO: add option to Display Prices with Tax Included
# Enabled the tax inclusive flag in the GetTax Request.
# constraints on uniq records creation with account_number and company_id
_sql_constraints = [
(
"code_company_uniq",
"unique (company_code)",
"Avalara setting is already available for this company code",
),
(
"account_number_company_uniq",
"unique (account_number, company_id)",
"The account number must be unique per company!",
),
]
def get_avatax_rest_service(self):
self.ensure_one()
if self.disable_tax_calculation:
_logger.info(
"Avatax tax calculation is disabled, skipping Avatax API contact."
)
return False
return AvaTaxRESTService(
self.account_number,
self.license_key,
self.service_url,
self.request_timeout,
self.logging,
config=self,
)
def create_transaction(
self,
doc_date,
doc_code,
doc_type,
partner,
ship_from_address,
shipping_address,
lines,
user=None,
exemption_number=None,
exemption_code_name=None,
commit=False,
invoice_date=None,
reference_code=None,
location_code=None,
is_override=None,
currency_id=None,
ignore_error=None,
log_to_record=False,
):
self.ensure_one()
avatax_config = self
currency_code = self.env.company.currency_id.name
if currency_id:
currency_code = currency_id.name
if not partner.customer_code:
if not avatax_config.auto_generate_customer_code:
raise UserError(
_(
"Customer Code for customer %(partner.name)s not defined.\n\n "
"You can edit the Customer Code in customer profile. "
'You can fix by clicking "Generate Customer Code" '
"button in the customer contact information"
)
)
else:
partner.generate_cust_code()
if not shipping_address:
raise UserError(
_("There is no source shipping address defined for partner %s.")
% partner.name
)
if not ship_from_address:
raise UserError(_("There is no Company address defined."))
if avatax_config.validation_on_save:
for address in [partner, shipping_address, ship_from_address]:
if not address.date_validation:
address.multi_address_validation(validation_on_save=True)
# this condition is required, in case user select force address validation
# on AvaTax API Configuration
if (
avatax_config.force_address_validation
and not avatax_config.disable_address_validation
):
if not shipping_address.date_validation:
raise UserError(
_(
"Please validate the shipping address for the partner %(partner.name)s."
)
)
# if not avatax_config.address_validation:
if not ship_from_address.date_validation:
raise UserError(_("Please validate the origin warehouse address."))
if avatax_config.disable_tax_calculation:
_logger.info(
"Avatax tax calculation is disabled. Skipping %s %s.",
doc_code,
doc_type,
)
return False
if commit and avatax_config.disable_tax_reporting:
_logger.warning(
_("Avatax commiting document %s, but it tax reporting is disabled."),
doc_code,
)
avatax = self.get_avatax_rest_service()
result = avatax.get_tax(
avatax_config.company_code,
doc_date,
doc_type,
partner.customer_code,
doc_code,
ship_from_address,
shipping_address,
lines,
exemption_number,
exemption_code_name,
user and user.name or None,
commit and not avatax_config.disable_tax_reporting,
invoice_date,
reference_code,
location_code,
currency_code,
partner.vat or None,
is_override,
ignore_error=ignore_error,
log_to_record=log_to_record,
)
return result
def commit_transaction(self, doc_code, doc_type):
self.ensure_one()
result = False
if not self.disable_tax_reporting:
avatax = self.get_avatax_rest_service()
result = avatax.call(
"commit_transaction", self.company_code, doc_code, {"commit": True}
)
return result
def void_transaction(self, doc_code, doc_type):
if self:
self.ensure_one()
result = False
if not self.disable_tax_reporting:
avatax = self.get_avatax_rest_service()
result = avatax.call(
"void_transaction",
self.company_code,
doc_code,
{"code": "DocVoided"},
)
return result
def unvoid_transaction(self, doc_code, doc_type):
self.ensure_one()
result = False
if not self.disable_tax_reporting:
avatax = self.get_avatax_rest_service()
result = avatax.call("unvoid_transaction", self.company_code, doc_code)
return result
def ping(self):
client = AvaTaxRESTService(config=self)
client.ping()
return True