Skip to content

Commit

Permalink
[ADD] stock_analysis_aeroo_report
Browse files Browse the repository at this point in the history
  • Loading branch information
ivantodorovich committed Nov 14, 2018
1 parent f0f76b4 commit 1b80210
Show file tree
Hide file tree
Showing 10 changed files with 492 additions and 0 deletions.
68 changes: 68 additions & 0 deletions stock_analysis_aeroo_report/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
.. |company| replace:: ADHOC SA

.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
:alt: ADHOC SA
:target: https://www.adhoc.com.ar

.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

=====================
Stock Analysis Report
=====================

This module adds a stock analysis report that shows information usefull to do forecasting.

Installation
============

To install this module, you need to:

#. Just install this module.

Configuration
=============

To configure this module, you need to:

#. Nothing to configure

Usage
=====

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: http://runbot.adhoc.com.ar/

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/ingadhoc/account-financial-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Images
------

* |company| |icon|

Contributors
------------

* Iván Todorovich <ivan.todorovich@gmail.com>

Maintainer
----------

|company_logo|

This module is maintained by the |company|.

To contribute to this module, please visit https://www.adhoc.com.ar.
6 changes: 6 additions & 0 deletions stock_analysis_aeroo_report/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from . import reports
from . import wizard
47 changes: 47 additions & 0 deletions stock_analysis_aeroo_report/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
##############################################################################
#
# Copyright (C) 2015 ADHOC SA (http://www.adhoc.com.ar)
# All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Stock Analysis Report',
'version': '11.0.1.0.0',
'category': 'Aeroo Reporting',
'sequence': 14,
'summary': '',
'author': 'ADHOC SA, Iván Todorovich <ivan.todorovich@gmail.com>',
'website': 'www.adhoc.com.ar',
'license': 'AGPL-3',
'images': [
],
'depends': [
'stock',
'account',
'report_aeroo',
],
'data': [
'reports/stock_analysis_report.xml',
'wizard/stock_analysis_report_wizard.xml',
],
'demo': [
],
'test': [
],
'installable': True,
'auto_install': False,
'application': False,
}
5 changes: 5 additions & 0 deletions stock_analysis_aeroo_report/reports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from . import stock_analysis_report_parser
Binary file not shown.
20 changes: 20 additions & 0 deletions stock_analysis_aeroo_report/reports/stock_analysis_report.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="action_aeroo_stock_analysis_report" model="ir.actions.report">
<field name="name">Stock Analysis Report</field>
<field name="model">product.product</field>
<field name="report_name">stock_analysis_report</field>
<field name="report_type">aeroo</field>
<field name="in_format">oo-ods</field>
<field name="parser_state">loc</field>
<field name="parser_loc">stock_analysis_aeroo_report/reports/stock_analysis_report_parser.py</field>
<!--<field name="report_file">stock_analysis_aeroo_report/reports/prueba.ods</field>-->
<field name="report_file">stock_analysis_aeroo_report/reports/stock_analysis_report.ods</field>
<field name="tml_source">file</field>
<field name="out_format" ref="report_aeroo.report_mimetypes_xls_odt"/>
<field name="binding_model_id" ref="product.model_product_product"/>
<field name="binding_type">report</field>
</record>

</odoo>
196 changes: 196 additions & 0 deletions stock_analysis_aeroo_report/reports/stock_analysis_report_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from odoo import api, models, _
from odoo.tools.safe_eval import safe_eval
from datetime import datetime
from dateutil.relativedelta import relativedelta
import math


def base_round(num, base=5):
return int(math.ceil(num/(base*1.0))*base)


def smart_round(num):
if num > 20000:
return base_round(num, 5000)
elif num > 10000:
return base_round(num, 2500)
elif num > 5000:
return base_round(num, 1000)
elif num > 2000:
return base_round(num, 500)
elif num > 1000:
return base_round(num, 250)
elif num > 500:
return base_round(num, 100)
elif num > 200:
return base_round(num, 50)
elif num > 100:
return base_round(num, 25)
elif num > 50:
return base_round(num, 10)
else:
return base_round(num, 5)


# Get a list of months names starting from the from_date
def get_last_months(from_date, to_date=False):
if not to_date:
to_date = datetime.today()
curr_date = from_date
while curr_date <= to_date:
yield curr_date.strftime('%B %Y')
curr_date += relativedelta(months=1)


class Parser(models.AbstractModel):
_inherit = 'report.report_aeroo.abstract'
_name = 'report.stock_analysis_report_parser'

@api.model
def aeroo_report(self, docids, data):
if not data:
data = {}

self.context = self.env.context
self.config = dict(self.context)
self.from_date = self.context.get('from_date')
self.to_date = self.context.get('to_date')
self.months = list(get_last_months(
datetime.strptime(self.from_date, '%Y-%m-%d'),
datetime.strptime(
self.to_date,
'%Y-%m-%d'
) if self.to_date else False))

domain = self.context.get('filter_domain')
self.domain = safe_eval(domain) if domain else []

data.update({
'config': self.config,
'from_date': self.from_date,
'to_date': self.to_date,
'months': self.months,
'get_data': self.get_data,
})

return super(Parser, self).aeroo_report(docids, data)

def get_data(self):

# get products
domain = self.domain
domain.append(('active', '=', 1))
domain.append(('type', '=', 'product'))
product_ids = self.env['product.product'].search(
domain, order='categ_id, default_code, name')

# prepare stock move domains
common_domain = []
common_domain.append(('state', '=', 'done'))
if self.context.get('from_date'):
common_domain.append(('date', '>=', self.context.get('from_date')))
if self.context.get('to_date'):
common_domain.append(('date', '<', self.context.get('to_date')))

# out domain
out_domain = list(common_domain)
if self.context.get('location_id'):
out_domain.append(
('location_id', '=', self.context.get('location_id')))
else:
out_domain.append(
('location_dest_id.usage', 'in', ['internal']))

# in domain
in_domain = list(common_domain)
if self.context.get('location_id'):
in_domain.append(
('location_dest_id', '=', self.context.get('location_id')))
else:
in_domain.append(('location_id.usage', 'not in', ['internal']))

# compile information
res = []
for p in product_ids:

# get moves month by month
odomain = list(out_domain)
idomain = list(in_domain)
odomain.append(('product_id', '=', p.id))
idomain.append(('product_id', '=', p.id))
om = self.env['stock.move'].read_group(
odomain,
groupby='date:month',
fields=['product_id', 'date', 'product_qty'])
im = self.env['stock.move'].read_group(
idomain,
groupby='date:month',
fields=['product_id', 'date', 'product_qty'])

# compile month by month dict
months = dict((el, 0) for el in self.months)
for m in om:
months[m['date:month']] = \
months.get(m['date:month'], 0) + m['product_qty']
for m in im:
months[m['date:month']] = \
months.get(m['date:month'], 0) - m['product_qty']

r = {
'product_id': p,
'default_code': p.default_code,
'name': p.name,
'reordering_min_qty': int(p.reordering_min_qty),
'reordering_max_qty': int(p.reordering_max_qty),
'seller_delay': max([s.delay for s in p.seller_ids] or [30]),
'qty_available': p.qty_available,
'months': months,
}

# moves by month
# compile data for analysis
month_data = [months.get(el, 0) for el in self.months]
# remove zero or lower readings
data = list(filter(lambda x: x > 0, month_data))
# remove possible out-of-stock months
if len(data) > 4:
for i in range(1, int(len(data)/2)):
if (min(data) < (max(data)*0.5)):
data = filter(lambda x: x > min(data), data)
else:
break
# avg remaining data
monthly = \
round(sum(data)/(len(data)*1.0), 0) if len(data) > 0 else 0.0
r['monthly'] = monthly

calculated_min = max([((r['seller_delay']*1.3)/30), 1])*monthly
r['suggested_min'] = smart_round(calculated_min)
r['suggested_max'] = smart_round(calculated_min*2)
r['stock_months'] = round(r['qty_available'] / monthly, 1) \
if monthly > 0 else \
('Infinito' if r['qty_available'] > 0 else False)

r['obs_stock'] = ''
r['obs_stock'] = _('LOW STOCK') if (
(r['qty_available']) < r['suggested_min']) else r['obs_stock']

r['obs_stock'] = _('OVERSTOCK') if (
(r['qty_available']) > r['suggested_max']) else r['obs_stock']

res.append(r)

# Filter according to context
# TODO: Implement filters based on stock analysis
# if self.config_filter == 'not_ok':
# res = filter(lambda x: x.get('obs') != '', res)
# if self.config_filter == 'only_low':
# res = filter(lambda x: 'LOW' in x.get('obs'), res)
# if self.config_filter == 'low_stock':
# res = filter(lambda x: x.get('obs_stock') != '', res)

return res
5 changes: 5 additions & 0 deletions stock_analysis_aeroo_report/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from . import stock_analysis_report_wizard
Loading

0 comments on commit 1b80210

Please sign in to comment.