How to inherit and modify Odoo Tax Calculation? - tax

I wanna inherit and modify odoo default tax calculation in odoo_15 Community because Odoo default tax calculation is losing over 100 kyats from our revenue. How can I Inherit and Modify Odoo Tax Formulation?

You could extend (inherit) the AccountTaxPython class, to inject your own Tax computation (and your own custom parameters, like in my case : nbpersons_zsb)
# ODOO version 13
# source of the AccountTaxPython class : addons/account_tax_python/account_tax.py
# inherited class here:
class AccountTaxPython(models.Model):
_inherit = "account.tax"
# original _compute_amount def in addons/account/models/account.py, chged in addons/account_tax_python/account_tax.py
def _compute_amount(self, base_amount, price_unit, quantity=1.0, product=None, partner=None):
self.ensure_one()
if product and product._name == 'product.template':
product = product.product_variant_id
# WILL ONLY AFFECT PYTHON CODED TAXES (FILTER == code)
if self.amount_type == 'code':
company = self.env.company
if self._context.get('nbpersons_zsb'):
nbpersons_zsb = self._context.get('nbpersons_zsb')
# if request.session['number_of_person']:
# nbpersons_zsb = request.session['number_of_person']
localdict = {'nbpersons_zsb': nbpersons_zsb, 'base_amount': base_amount, 'price_unit': price_unit,
'quantity': quantity, 'product': product, 'partner': partner, 'company': company}
# localdict = {'base_amount': base_amount, 'price_unit':price_unit, 'quantity': quantity, 'product':product, 'partner':partner, 'company': company}
safe_eval(self.python_compute, localdict, mode="exec", nocopy=True)
return localdict['result']
return super(AccountTaxPython, self)._compute_amount(base_amount, price_unit, quantity, product, partner)
def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, partner=None, is_refund=False,
handle_price_include=True):
taxes = self.filtered(lambda r: r.amount_type != 'code')
company = self.env.company
if product and product._name == 'product.template':
product = product.product_variant_id
# WILL ONLY AFFECT PYTHON CODED TAXES (FILTER == code)
for tax in self.filtered(lambda r: r.amount_type == 'code'):
localdict = self._context.get('tax_computation_context', {})
if self._context.get('nbpersons_zsb'):
nbpersons_zsb = self._context.get('nbpersons_zsb')
# if request.session['number_of_person']:
# nbpersons_zsb = request.session['number_of_person']
localdict.update(
{'nbpersons_zsb': nbpersons_zsb, 'price_unit': price_unit, 'quantity': quantity, 'product': product,
'partner': partner, 'company': company})
# localdict.update({'price_unit':price_unit,'quantity':quantity,'product':product,'partner':partner,'company':company})
safe_eval(tax.python_applicable, localdict, mode="exec", nocopy=True)
if localdict.get('result', False):
taxes += tax
return super(AccountTaxPython, taxes).compute_all(price_unit, currency, quantity, product, partner,
is_refund=is_refund,
handle_price_include=handle_price_include)

Related

Django: aggregate if all boolean model fields is True

from django.db import models
class Car(models.model):
sold = models.BooleanField(default=False)
I can detemine if all cars were sold by making two queries:
sold_count = Car.objects.filter(sold=True).count()
all_count = Car.objects.count()
are_all_sold = (all_count - sold_count) == 0
Since this operation is very frequent on my app, I am wondering if it is possible to do it in just one DB query? e.g. using Aggregation or Query Expressions, etc.
just an update, I can get the stats on how many were sold & unsold by one single query:
Car.objects.values("sold").annotate(count=Count("sold"))
<QuerySet [{'sold': False, 'count': 1}, {'sold': True, 'count': 9}]>
Yes. You can do that by using Model Manager.
in model.py:
from django.db import models
class CarManager(models.Manager):
def are_all_sold_or_not(self):
sold_count = Car.objects.filter(sold=True).count()
all_count = Car.objects.count()
return all_count == sold_count
class Car(models.model):
sold = models.BooleanField(default=False)
objects = CarManager()
in views.py
def myview(request):
.....
.
are_all_sold = Car.objects.are_all_sold_or_not()
.
.
You can read more about Model Manager in the documentation:
https://docs.djangoproject.com/en/4.0/topics/db/managers/

How to bulk create or update in Django

I have to process an item report CSV file every 1 hour. The CSV contains 150k+ records for 1 account and there are multiple accounts in my system. I was working previously on rails and there was active record gem to handle this use case very efficiently. I am looking for an alternate to this gem in Django or any built in method that will be helpful to import such large data in bulk.
So far I have tried this code.
class ItemReportService:
def call(self, file_url):
with open(file_url, 'r') as file:
reader = csv.DictReader(file)
products = []
for row in reader:
product = self.process_product(row)
products.append(product)
self.update_products(products)
def process_product(self, row):
print(f'Processing sku: {row["SKU"]}')
product = Product.objects.filter(
sku=row['SKU']).first() or Product(sku=row['SKU'])
product.listing_title = row['Product Name']
product.listed_price = row['Price']
product.buy_box_price = row['Buy Box Item Price'] + \
row['Buy Box Shipping Price']
product.status = row['Lifecycle Status']
return product
def update_products(self, products):
Product.objects.bulk_update(
products,
[
'listing_title',
'listed_price',
'buy_box_price',
'Lifecycle Status'
]
)
It is raising this exception because when there is a new product it doesn't have primary key assigned to it
ValueError: All bulk_update() objects must have a primary key set.
Django 4.1 has new parameters for bulk_create(update_conflicts=bool and update_fields=[])
If your model has a field UNIQUE usually Django would ignore it when creating new data. But if you set the update_conflicts parameter to True, the fields inside update_fields will be updated.
You are not saving the product in the database before applying bulk_update.
I have checked your code for this purpose, you can use bulk_insert with an additional parameter
Model.objects.bulk_create(self.data, ignore_conflicts=True)
or
columns = ['column1', 'column2']
obj = Model.objects.filter(column1="sku").first()
if not obj:
obj = Model.objects.create(column1="sku")
obj.column1 = row["column1"] or obj.column1
obj.column2 = row["column2"] or obj.column2
items_to_be_inserted.append(obj)
In the end, you can do bulk update like
Model.objects.bulk_update(items_to_be_inserted, columns)
This will solve your problem.
I made this class function which can be used on any Django model in a project.
from django.db import models
class BaseModel(models.Model):
#classmethod
def bulk_create_or_update(
cls, uniques: list[str],
defaults: list[str],
data: list[dict]
):
# Get existing object list
data_dict, select = {}, None
for entry in data:
sub_entry, key = {}, ''
for uniq in uniques:
sub_entry[uniq] = entry[uniq]
key += str(entry[uniq])
data_dict[key] = entry
if not select:
select = models.Q(**sub_entry)
continue
select |= models.Q(**sub_entry)
records = cls.objects.filter(select).values('pk', *uniques)
existing = {}
for rec in records:
key = ''
for uniq in uniques:
key += str(rec[uniq])
existing[key] = rec
# Split new objects from existing ones
to_create, to_update = [], []
for key, entry in data_dict.items():
obj = cls(**entry)
if key not in existing:
to_create.append(obj)
continue
obj.pk = existing[key]['pk']
to_update.append(obj)
cls.objects.bulk_create(to_create, batch_size=1000)
cls.objects.bulk_update(to_create, defaults, batch_size=1000)
Let take an usage example
class Product(BaseModel)
price = models.IntegerField()
name = models.CharField(max_length=128, unique=True)
status = models.CharField(max_length=128)
if __name__ == '__main__':
data = [
{'price': 50, 'name': 'p1', 'status': 'New'},
{'price': 33, 'name': 'p2', 'status': 'Old'}
]
Product.bulk_create_or_update(uniques=['name'], defaults=['price', 'status'], data=data)
Any improvement suggestion of the code is welcome.

Save method of Django model does not update fields of existing record even if force update

I am trying to update the record that already exists in the database and therefore I use this code
if 'supplierId' not in req.keys():
return JsonResponse({'code': 0, 'msg': "supplier was not selected", 'result': ''}, safe=False)
assigneeId = User.objects.get(pk=req.get('assigneeId', 1))
responsibleId = User.objects.get(pk=req.get('responsibleId', 1))
redistributionMethod = req.get('redistributionMethod', 0)
amount = req.get('allCost', 0)
procurement_doc = ProcurementDocJournal.objects.get(id=pk)
print(procurement_doc)
procurement_doc.docType = req['docType']
procurement_doc.status = req['status']
procurement_doc.companyId = Company.objects.get(pk=req['companyId'])
procurement_doc.datetime = req['datetime']
procurement_doc.supplierId = Partner.objects.get(pk=req['supplierId'])
procurement_doc.assigneeId = assigneeId
procurement_doc.warehouseId = Warehouse.objects.get(pk=req['warehouseId'])
procurement_doc.responsibleId = responsibleId
procurement_doc.redistributionMethod = redistributionMethod
procurement_doc.amount = amount
procurement_doc.comment = req['comment']
procurement_doc.save(force_update=True, update_fields=['comment', 'amount', 'redistributionMethod',
'responsibleId', 'warehouseId',
'supplierId', 'datetime',
'companyId', 'assigneeId', 'status', 'docType'])
where req contains a request
something like this
{
'docType': 3,
'status': 1,
'companyId': '2',
'warehouseId': '3',
'assigneeId': '5',
'supplierId': '12671',
'responsibleId': '5',
'datetime': '2020-04-01 08:01:00',
'comment': ''
}
As you can see there is a print which assures me that I selected the correct row
when I noticed that these records are not updated I searched for causes and found
this question where the guy who asked says The message field was missing from the model definition
in my case, none of these are missing from the model's description
class ProcurementDocJournal(models.Model):
id = models.IntegerField(primary_key=True, null=False)
docNumber = models.IntegerField()
status = models.IntegerField()
docType = models.IntegerField()
depended = models.IntegerField()
companyId = models.ForeignKey(Company, on_delete=models.CASCADE,
db_column='companyId')
created_at = models.DateTimeField(auto_now_add=True)
datetime = models.DateTimeField()
currencyId = models.ForeignKey(Currency, db_column='currencyId', on_delete=models.CASCADE)
currencyRate = models.FloatField()
redistributionMethod = models.IntegerField()
assigneeId = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ProcurementDocJournal',
db_column='assigneeId')
warehouseId = models.ForeignKey(Warehouse, on_delete=models.CASCADE,
db_column='warehouseId')
responsibleId = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ProcurementDoc',
db_column='responsibleId')
supplierId = models.ForeignKey(Partner, on_delete=models.CASCADE,
db_column='supplierId')
amount = models.FloatField()
comment = models.TextField()
class Meta:
db_table = 'procurementDocJournal'
get_latest_by = 'id'
Edit
I have an action that contains
procurement_doc_journal_item = ProcurementDocJournal.objects.get(id=pk)
currencyId = req['currency']
currency = Currency.objects.get(id=currencyId)
currencyRate = CurrencyRate(date, currency.name)
procurement_doc_journal_item.currencyId = currency
procurement_doc_journal_item.currencyRate = currencyRate['rate']
procurement_doc_journal_item.save()
and works like a charm
there is no any error that logs say
You don't specify if there's any error in your logs. I'd kind of expect to see something since if it's not saving, it must be bombing out before that as no fields are mandatory.
I am, however, not sure you're setting your FKs correctly here:
procurement_doc.companyId = Company(req['companyId'])
Should be
procurement_doc.companyId = Company.objects.get(pk=req['companyId'])
and that assumes companyId is set to a value that exists of course ... Whether this is the source of your issue or not, I'm not sure. I don't see any of your FKs are mandatory.
Edit: I've just tested this on one of my models with a simple id key:
>>> Contact(1)
Contact()
>>> c = Contact(1)
>>> c.name
''
>>> c = Contact.objects.get(pk=1)
>>> c.name
'Mike'
It really doesn't seem to me as though it works ...
Sidenote: You can simplify code like this:
if 'allCost' not in req.keys():
amount = 0
else:
amount = req['allCost']
By doing:
amount = req.get('allCost', 0)

Assign the same value to object keys from lambda in Python (Django)

When trying to assign the same value to starting_bid and price from the Listing model in my Django project the below approach always yields different values. How can I assign the same value while still having the lambda return a random integer whenever the test runs?
tests.py
SEEDER = Seed.seeder() # from django_seed
AMOUNT = 15
MIN = 5
MAX = 84
PRICE = lambda x: random.randint(MIN, MAX)
SEEDER.add_entity(
Listing,
AMOUNT,
{
"starting_bid": PRICE,
"price": PRICE
})
SEEDER.execute()
Result:
{"starting_bid": 80, "price": 45}
Expected:
{"starting_bid": 80, "price": 80}
models.py
class Listing(models.Model):
CATEGORIES = (("LAP", "Laptop"), ("CON", "Console"), ("GAD", "Gadget"), ("GAM", "Game"), ("TEL", "TV"))
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=64)
description = models.CharField(max_length=256)
starting_bid = models.PositiveSmallIntegerField()
price = models.PositiveSmallIntegerField()
image_url = models.URLField(max_length=200)
category = models.CharField(max_length=8, choices=CATEGORIES)
active = models.BooleanField(default=True)
_price = []
def PRICE(round = 2):
global _price
if not _price:
_price = random.randint(MIN, MAX)
_price = [_price] * round
return _price.pop()

Half day leaves in payslip

I used Odoo 11 when the leaves request set with 0.5 days, it will be considered 1 day when I generate the payslip
I would like, when I set the request leaves with half days, It will be not round in payslips worked days
#api.model
def get_worked_day_lines(self, contracts, date_from, date_to):
"""
#param contract: Browse record of contracts
#return: returns a list of dict containing the input that should be applied for the given contract between date_from and date_to
"""
res = []
# fill only if the contract as a working schedule linked
for contract in contracts.filtered(lambda contract: contract.resource_calendar_id):
day_from = datetime.combine(fields.Date.from_string(date_from), datetime_time.min)
day_to = datetime.combine(fields.Date.from_string(date_to), datetime_time.max)
# compute leave days
leaves = {}
day_leave_intervals = contract.employee_id.iter_leaves(day_from, day_to, calendar=contract.resource_calendar_id)
for day_intervals in day_leave_intervals:
for interval in day_intervals:
holiday = interval[2]['leaves'].holiday_id
code = holiday.holiday_status_id.name
if holiday.holiday_status_id.impayed:
code = "Unpaid"
current_leave_struct = leaves.setdefault(holiday.holiday_status_id, {
'name': holiday.holiday_status_id.name,
'sequence': 5,
'code': code,
'number_of_days': 0.0,
'number_of_hours': 0.0,
'contract_id': contract.id,
})
leave_time = (interval[1] - interval[0]).seconds / 3600
current_leave_struct['number_of_hours'] += leave_time
work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id)
if work_hours:
current_leave_struct['number_of_days'] += leave_time / work_hours
# compute worked days
work_data = contract.employee_id.get_work_days_data(day_from, day_to, calendar=contract.resource_calendar_id)
attendances = {
'name': _("Normal Working Days paid at 100%"),
'sequence': 1,
'code': 'WORK100',
'number_of_days': work_data['days'],
'number_of_hours': work_data['hours'],
'contract_id': contract.id,
}
res.append(attendances)
res.extend(leaves.values())
return res

Resources