From 6006bc192627efc72c3c2e1d4141560991c75003 Mon Sep 17 00:00:00 2001 From: Chinmayi Vyas Date: Mon, 19 Mar 2018 15:13:43 +0530 Subject: [PATCH] [ADD]Stock Ageing qweb Report: lists the age-wise break-up of Inventory to point out old stock and displays the age of the stock in hand --- addons/stock/__init__.py | 15 +- addons/stock/__manifest__.py | 1 + addons/stock/models/stock_location.py | 7 + addons/stock/models/stock_move.py | 40 ++++ addons/stock/views/stock_move_views.xml | 3 + addons/stock_ageing_report/__init__.py | 5 + addons/stock_ageing_report/__manifest__.py | 31 +++ .../i18n/stock_ageing_report.pot | 189 ++++++++++++++++++ addons/stock_ageing_report/models/__init__.py | 3 + .../models/stock_location.py | 26 +++ addons/stock_ageing_report/report/__init__.py | 3 + .../report/stock_ageing_report.py | 161 +++++++++++++++ .../report/stock_ageing_report_template.xml | 86 ++++++++ .../report/stock_ageing_report_view.xml | 27 +++ .../static/description/icon.png | Bin 0 -> 6501 bytes addons/stock_ageing_report/tests/__init__.py | 3 + .../tests/test_stock_ageing_report.py | 29 +++ addons/stock_ageing_report/wizard/__init__.py | 3 + .../wizard/stock_ageing_wizard.py | 55 +++++ .../wizard/stock_ageing_wizard_view.xml | 50 +++++ 20 files changed, 734 insertions(+), 3 deletions(-) create mode 100644 addons/stock_ageing_report/__init__.py create mode 100644 addons/stock_ageing_report/__manifest__.py create mode 100644 addons/stock_ageing_report/i18n/stock_ageing_report.pot create mode 100644 addons/stock_ageing_report/models/__init__.py create mode 100644 addons/stock_ageing_report/models/stock_location.py create mode 100644 addons/stock_ageing_report/report/__init__.py create mode 100644 addons/stock_ageing_report/report/stock_ageing_report.py create mode 100644 addons/stock_ageing_report/report/stock_ageing_report_template.xml create mode 100644 addons/stock_ageing_report/report/stock_ageing_report_view.xml create mode 100644 addons/stock_ageing_report/static/description/icon.png create mode 100644 addons/stock_ageing_report/tests/__init__.py create mode 100644 addons/stock_ageing_report/tests/test_stock_ageing_report.py create mode 100644 addons/stock_ageing_report/wizard/__init__.py create mode 100644 addons/stock_ageing_report/wizard/stock_ageing_wizard.py create mode 100644 addons/stock_ageing_report/wizard/stock_ageing_wizard_view.xml diff --git a/addons/stock/__init__.py b/addons/stock/__init__.py index f506abac..5138ca84 100644 --- a/addons/stock/__init__.py +++ b/addons/stock/__init__.py @@ -5,9 +5,8 @@ from . import controllers from . import models from . import report from . import wizard - - -from flectra import api, SUPERUSER_ID +from flectra import api +from flectra.api import Environment, SUPERUSER_ID # TODO: Apply proper fix & remove in master @@ -17,3 +16,13 @@ def pre_init_hook(cr): ('model', 'like', '%stock%'), ('module', '=', 'stock') ]).unlink() + + +def post_init_check(cr, registery): + env = Environment(cr, SUPERUSER_ID, {}) + move_obj = env['stock.move'] + move_ids = move_obj.search([]) + move_ids.set_move_type() + done_moves = move_obj.search([('state', '=', 'done')], order='date') + done_moves.check_move_bal_qty() + return True diff --git a/addons/stock/__manifest__.py b/addons/stock/__manifest__.py index 908cf27f..dee8cbe1 100644 --- a/addons/stock/__manifest__.py +++ b/addons/stock/__manifest__.py @@ -86,4 +86,5 @@ 'application': True, 'auto_install': False, 'pre_init_hook': 'pre_init_hook', + 'post_init_hook': 'post_init_check', } diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 5f20bf2e..b569bd56 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -25,6 +25,13 @@ class Location(models.Model): res['barcode'] = res['complete_name'] return res + def _should_be_valued(self): + self.ensure_one() + if self.usage == 'internal' or ( + self.usage == 'transit' and self.company_id): + return True + return False + name = fields.Char('Location Name', required=True, translate=True) complete_name = fields.Char("Full Location Name", compute='_compute_complete_name', store=True) active = fields.Boolean('Active', default=True, help="By unchecking the active field, you may hide a location without deleting it.") diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 6ac00aa9..3b05bd53 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -159,6 +159,9 @@ class StockMove(models.Model): is_quantity_done_editable = fields.Boolean('Is quantity done editable', compute='_compute_is_quantity_done_editable') reference = fields.Char(compute='_compute_reference', string="Reference", store=True) has_move_lines = fields.Boolean(compute='_compute_has_move_lines') + out_qty = fields.Float("Out Qty") + bal_qty = fields.Float("Balance") + move_type = fields.Selection([('in', 'In'), ('out', 'Out')], "Move type") branch_id = fields.Many2one( related='location_id.branch_id', store=True, string='Source Location Branch', @@ -394,6 +397,17 @@ class StockMove(models.Model): move.location_id.name, move.location_dest_id.name))) return res + def set_move_type(self): + for self in self: + if self.picking_type_id and self.picking_type_id.code == 'incoming' or\ + not self.location_id._should_be_valued() and \ + self.location_dest_id._should_be_valued(): + self.move_type = 'in' + elif self.picking_type_id and self.picking_type_id.code == 'outgoing' \ + or self.location_id._should_be_valued() and not \ + self.location_dest_id._should_be_valued(): + self.move_type = 'out' + @api.model def create(self, vals): # TDE CLEANME: why doing this tracking on picking here ? seems weird @@ -403,6 +417,7 @@ class StockMove(models.Model): initial_values = {picking.id: {'state': picking.state}} vals['ordered_qty'] = vals.get('product_uom_qty') res = super(StockMove, self).create(vals) + res.set_move_type() if perform_tracking: picking.message_track(picking.fields_get(['state']), initial_values) return res @@ -1045,6 +1060,28 @@ class StockMove(models.Model): break return extra_move + def check_move_bal_qty(self): + for move in self: + if move.move_type == 'in': + move.bal_qty = move.product_uom_qty + if move.move_type == 'out': + prev_in_moves = self.search([ + ('product_id', '=', move.product_id.id), + ('state', '=', 'done'), + ('move_type', '=', 'in'), + ('bal_qty', '>', '0'), + ], order='date') + rem_qty = move.product_uom_qty + for pre_move in prev_in_moves: + if rem_qty: + pre_move.out_qty = move.product_uom_qty + if rem_qty > pre_move.bal_qty: + rem_qty -= pre_move.bal_qty + pre_move.bal_qty = 0 + else: + pre_move.bal_qty -= rem_qty + rem_qty -= rem_qty + def _action_done(self): self.filtered(lambda move: move.state == 'draft')._action_confirm() # MRP allows scrapping draft moves @@ -1105,6 +1142,9 @@ class StockMove(models.Model): if picking: picking._create_backorder() + #calculate balance quantity & out qty to track the age of each stock + moves_todo.check_move_bal_qty() + return moves_todo def unlink(self): diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml index 24321c38..db7bea48 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -238,6 +238,9 @@ + + + diff --git a/addons/stock_ageing_report/__init__.py b/addons/stock_ageing_report/__init__.py new file mode 100644 index 00000000..e2c04cfe --- /dev/null +++ b/addons/stock_ageing_report/__init__.py @@ -0,0 +1,5 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from . import wizard +from . import report +from . import models diff --git a/addons/stock_ageing_report/__manifest__.py b/addons/stock_ageing_report/__manifest__.py new file mode 100644 index 00000000..3bb1bea8 --- /dev/null +++ b/addons/stock_ageing_report/__manifest__.py @@ -0,0 +1,31 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Stock Ageing Report', + 'version': '1.0', + 'category': 'Stock', + 'author': 'FlectraHQ', + 'website': "https://flectrahq.com", + 'depends': ['stock'], + 'sequence': 40, + 'summary': """Stock Ageing Report to display + product's stock by ageing period.""", + 'description': """ +Main Features +------------- +Stock Ageing Reports. +An Ageing Analysis Report in Flectra displays the age of the stock in hand. +This report lists the age-wise break-up of Inventory to point out old stock. +Flectra gives its users the flexibility to define their own ageing slabs. +""", + 'data': [ + 'wizard/stock_ageing_wizard_view.xml', + 'report/stock_ageing_report_view.xml', + 'report/stock_ageing_report_template.xml', + ], + 'demo': [], + 'qweb': [], + 'auto_install': False, + 'installable': True, + 'application': False, +} diff --git a/addons/stock_ageing_report/i18n/stock_ageing_report.pot b/addons/stock_ageing_report/i18n/stock_ageing_report.pot new file mode 100644 index 00000000..7d0b2f08 --- /dev/null +++ b/addons/stock_ageing_report/i18n/stock_ageing_report.pot @@ -0,0 +1,189 @@ +# Translation of Flectra Server. +# This file contains the translation of the following modules: +# * stock_ageing_report +# +msgid "" +msgstr "" +"Project-Id-Version: Flectra Server 1.2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-05-15 11:07+0000\n" +"PO-Revision-Date: 2018-05-15 11:07+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Brnach: " +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Company: " +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Date: " +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Location: " +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Period Length(days): " +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Product Category: " +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Warehouse: " +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_branch_id +msgid "Branch" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_wizard_form_view +msgid "Cancel" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,help:stock_ageing_report.field_stock_ageing_wizard_date +msgid "Choose a date to get the inventory ageing report" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_company_id +msgid "Company" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_create_uid +msgid "Created by" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_create_date +msgid "Created on" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_date +msgid "Date" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_report_stock_ageing_report_stock_ageing_report_template_display_name +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_display_name +msgid "Display Name" +msgstr "" + +#. module: stock_ageing_report +#: code:addons/stock_ageing_report/report/stock_ageing_report.py:17 +#, python-format +msgid "Form content is missing, this report cannot be printed." +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_report_stock_ageing_report_stock_ageing_report_template_id +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_id +msgid "ID" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model,name:stock_ageing_report.model_stock_location +msgid "Inventory Locations" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_report_stock_ageing_report_stock_ageing_report_template___last_update +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard___last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_write_date +msgid "Last Updated on" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_location_ids +msgid "Location" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_period_length +msgid "Period Length (days)" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_wizard_form_view +msgid "Print" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_product_ids +msgid "Product" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_product_category_ids +msgid "Product Category" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Products" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_wizard_form_view +msgid "Select following details" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.actions.act_window,name:stock_ageing_report.action_stock_ageing_wizard +#: model:ir.actions.report,name:stock_ageing_report.action_stock_ageing_report +#: model:ir.ui.menu,name:stock_ageing_report.stock_ageing_wizard_menu +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Stock Ageing Report" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.ui.view,arch_db:stock_ageing_report.stock_ageing_report_template +msgid "Total" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model.fields,field_description:stock_ageing_report.field_stock_ageing_wizard_warehouse_ids +msgid "Warehouse" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model,name:stock_ageing_report.model_stock_ageing_wizard +msgid "Wizard that opens the stock ageing" +msgstr "" + +#. module: stock_ageing_report +#: model:ir.model,name:stock_ageing_report.model_report_stock_ageing_report_stock_ageing_report_template +msgid "report.stock_ageing_report.stock_ageing_report_template" +msgstr "" + diff --git a/addons/stock_ageing_report/models/__init__.py b/addons/stock_ageing_report/models/__init__.py new file mode 100644 index 00000000..6bb763b0 --- /dev/null +++ b/addons/stock_ageing_report/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from . import stock_location diff --git a/addons/stock_ageing_report/models/stock_location.py b/addons/stock_ageing_report/models/stock_location.py new file mode 100644 index 00000000..9618aa46 --- /dev/null +++ b/addons/stock_ageing_report/models/stock_location.py @@ -0,0 +1,26 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from flectra import models, api + + +class Location(models.Model): + _inherit = "stock.location" + + @api.model + def name_search(self, name='', args=None, operator='ilike', limit=100): + args = args or [] + domain = [] + + if self.env.context.get('warehouse', False): + warehouse_ids = self.env['stock.warehouse'].browse( + self.env.context['warehouse'][0][2]) + lot_stock_ids = [wh.lot_stock_id.id for wh in warehouse_ids + if wh.lot_stock_id] + location_ids = self.env['stock.location'].search( + [('location_id', 'child_of', lot_stock_ids), + ('usage', '=', 'internal')]).ids + location_ids += lot_stock_ids + domain = [('id', 'in', location_ids)] + + recs = self.search(domain + args, limit=limit) + return recs.name_get() diff --git a/addons/stock_ageing_report/report/__init__.py b/addons/stock_ageing_report/report/__init__.py new file mode 100644 index 00000000..12976133 --- /dev/null +++ b/addons/stock_ageing_report/report/__init__.py @@ -0,0 +1,3 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from . import stock_ageing_report diff --git a/addons/stock_ageing_report/report/stock_ageing_report.py b/addons/stock_ageing_report/report/stock_ageing_report.py new file mode 100644 index 00000000..8e0fb5c5 --- /dev/null +++ b/addons/stock_ageing_report/report/stock_ageing_report.py @@ -0,0 +1,161 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from flectra import api, models, _ +from flectra.exceptions import UserError +from datetime import datetime, timedelta +from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT + + +class StockAgeingReport(models.AbstractModel): + _name = 'report.stock_ageing_report.stock_ageing_report_template' + + @api.model + def get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get( + 'active_model') or not self.env.context.get('active_id'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + report = self.env['ir.actions.report']._get_report_from_name( + 'stock_ageing_report.action_stock_ageing_report') + get_data = self._get_data(data) + return { + 'doc_ids': docids, + 'doc_model': report.model, + 'docs': self, + 'data': { + 'company': [get_data['company_id']], + 'branch': [get_data['branch_id']], + 'warehouse': [get_data['warehouse_id']], + 'location': [get_data['location_id']], + 'product_category': [get_data['product_category_id']], + 'product': [get_data['product_id']], + 'period_length': [get_data['period_length']], + 'date': [get_data['date']], + 'period_list': get_data['period_list'], + 'product_list': get_data['product_list'] + } + } + + def _get_product_wise_detail(self, product_ids, location_id, date, + period_length, branch_id, company_id): + product_list = [] + date_1 = datetime.strftime(date, DATETIME_FORMAT) + date_2 = datetime.strftime( + date - timedelta(days=period_length), DATETIME_FORMAT) + date_3 = datetime.strftime( + date - timedelta(days=(period_length * 2)), DATETIME_FORMAT) + date_4 = datetime.strftime( + date - timedelta(days=(period_length * 3)), DATETIME_FORMAT) + date_5 = datetime.strftime( + date - timedelta(days=(period_length * 4)), DATETIME_FORMAT) + date_period = [date_1, date_2, date_3, date_4, date_5] + final_dates = [ + (date, date_period[date_period.index(date) + 1] if + date_period.index(date) < 4 else date) + for date in date_period] + for product in product_ids: + qty_period_list = self.calculate_virtual_qty( + product, location_id, branch_id, company_id, final_dates) + product_dict = {'product_id': product.name_get()[0][1], + 'qty_period_list': qty_period_list, + 'qty_available': sum(qty_period_list)} + product_list.append(product_dict) + return product_list + + def calculate_virtual_qty(self, product, location_id, branch_id, + company_id, date_range): + product_list = [] + quant_obj = self.env['stock.quant'] + move_obj = self.env['stock.move'] + for date in date_range: + domain = [] + to_date = False + from_date = date[0] + if date[0] != date[1]: + to_date = date[1] + domain += [('product_id', '=', product.id)] + if product.tracking != 'none': + domain += [('location_id', 'in', location_id.ids), + ('in_date', '<=', from_date), + ('branch_id', '=', branch_id.id), + ('company_id', '=', company_id.id)] + if to_date: + domain += [('in_date', '>', to_date)] + virtual_available = sum( + [quant.quantity for quant in quant_obj.search(domain)]) + print ("\n domain", domain) + else: + domain += ['|', ('location_id', 'in', location_id.ids), + ('location_dest_id', 'in', location_id.ids), + ('state', '=', 'done'), ('bal_qty', '>', 0), + ('date', '<=', from_date), + ('branch_dest_id', '=', branch_id.id), + ('company_id', '=', company_id.id)] + if to_date: + domain += [('date', '>', to_date)] + virtual_available = sum( + [move.bal_qty for move in move_obj.search(domain)]) + product_list.append(virtual_available) + return product_list + + def _get_data(self, data): + domain = [] + company_id = self.env['res.company'].browse(data['form']['company_id']) + branch_id = self.env['res.branch'].browse(data['form']['branch_id']) + warehouse_id = self.env['stock.warehouse'].browse( + data['form']['warehouse_id']) + location_id = self.env['stock.location'].browse( + data['form']['location_id']) + product_category_id = self.env['product.category'].browse( + data['form']['product_category_id']) + product_id = self.env['product.product'].browse( + data['form']['product_id']) + if not location_id: + location_id = self.env['stock.location'].search([ + ('company_id', '=', company_id.id), + ('usage', '=', 'internal')]) + period_length = data['form']['period_length'] + date = datetime.strptime(data['form']['date'], DATETIME_FORMAT) + + if product_id: + domain += [('id', 'in', product_id.ids)] + elif product_category_id: + domain += [('categ_id', 'in', product_category_id.ids), + ('type', '=', 'product')] + else: + domain += [('qty_available', '>', 0)] + product_ids = self.env['product.product'].search(domain) + product_list = self._get_product_wise_detail( + product_ids, location_id, date, period_length, branch_id, + company_id) + warehouse = location = product_category = '' + if warehouse_id: + warehouse = ','.join( + wh.name_get()[0][1] for wh in + warehouse_id) if len(warehouse_id) > 1 else\ + warehouse_id.name_get()[0][1] + if data['form']['location_id']: + location = ','.join(loc.name_get()[0][1] for loc in location_id) \ + if len(location_id) > 1 else location_id.name_get()[0][1] or '' + if product_category_id: + product_category = ','.join( + categ.name_get()[0][1] for categ in product_category_id + ) if len(product_category_id) > 1 else\ + product_category_id.name_get()[0][1] or '' + period_list = ['0 - ' + str(period_length), + str(period_length) + ' - ' + str(period_length * 2), + str(period_length * 2) + ' - ' + str(period_length * 3), + str(period_length * 3) + ' - ' + str(period_length * 4), + ' + ' + str(period_length * 4)] + data['form'].update({ + 'company_id': company_id, + 'branch_id': branch_id, + 'warehouse_id': warehouse or '', + 'location_id': location or '', + 'product_category_id': product_category or '', + 'product_id': product_id or '', + 'period_length': period_length, + 'date': datetime.strftime(date, DATETIME_FORMAT), + 'period_list': period_list, + 'product_list': product_list}) + return data['form'] diff --git a/addons/stock_ageing_report/report/stock_ageing_report_template.xml b/addons/stock_ageing_report/report/stock_ageing_report_template.xml new file mode 100644 index 00000000..e142204b --- /dev/null +++ b/addons/stock_ageing_report/report/stock_ageing_report_template.xml @@ -0,0 +1,86 @@ + + + + \ No newline at end of file diff --git a/addons/stock_ageing_report/report/stock_ageing_report_view.xml b/addons/stock_ageing_report/report/stock_ageing_report_view.xml new file mode 100644 index 00000000..74b0e4c7 --- /dev/null +++ b/addons/stock_ageing_report/report/stock_ageing_report_view.xml @@ -0,0 +1,27 @@ + + + + + + Stock Ageing Report Format + + A4 + 0 + 0 + Portrait + 40 + 23 + 7 + 7 + + 40 + 90 + + + + 'stock_ageing'+'-'+(object.name) + + + \ No newline at end of file diff --git a/addons/stock_ageing_report/static/description/icon.png b/addons/stock_ageing_report/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd4caac27a08df401dea0f07c649e359748f1f5f GIT binary patch literal 6501 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 ZF4gX1RpL}jEbvPNq^_c^T&-vm^*_MaoPz)W literal 0 HcmV?d00001 diff --git a/addons/stock_ageing_report/tests/__init__.py b/addons/stock_ageing_report/tests/__init__.py new file mode 100644 index 00000000..ed0b1b9c --- /dev/null +++ b/addons/stock_ageing_report/tests/__init__.py @@ -0,0 +1,3 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from . import test_stock_ageing_report diff --git a/addons/stock_ageing_report/tests/test_stock_ageing_report.py b/addons/stock_ageing_report/tests/test_stock_ageing_report.py new file mode 100644 index 00000000..1caf0bed --- /dev/null +++ b/addons/stock_ageing_report/tests/test_stock_ageing_report.py @@ -0,0 +1,29 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from flectra.tests.common import TransactionCase +from datetime import date, timedelta + + +class TestStockAgeingReport(TransactionCase): + def setUp(self): + super(TestStockAgeingReport, self).setUp() + self.location = self.env['stock.location'] + self.location_barcode = self.env.ref('stock.stock_location_3') + self.barcode = self.location_barcode.barcode + self.warehouse = self.env["stock.warehouse"] + + def test_20_stock_ageing_report(self): + # Print the Stock Ageing Report through the wizard + data_dict = { + 'company_id': [], + 'warehouse_ids': [], + 'location_ids': [], + 'product_category_ids': [(6, 0, [self.env.ref( + 'product.product_category_5').id])], + 'product_ids': [(6, 0, [self.env.ref( + 'product.product_product_5b').id])], + 'period_length': 30, + 'date': date.today() - timedelta(days=30), + } + wizard = self.env['stock.ageing.wizard'].create(data_dict) + wizard.with_context(data_dict).print_report() diff --git a/addons/stock_ageing_report/wizard/__init__.py b/addons/stock_ageing_report/wizard/__init__.py new file mode 100644 index 00000000..c8c4a5ba --- /dev/null +++ b/addons/stock_ageing_report/wizard/__init__.py @@ -0,0 +1,3 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from . import stock_ageing_wizard diff --git a/addons/stock_ageing_report/wizard/stock_ageing_wizard.py b/addons/stock_ageing_report/wizard/stock_ageing_wizard.py new file mode 100644 index 00000000..21c0710c --- /dev/null +++ b/addons/stock_ageing_report/wizard/stock_ageing_wizard.py @@ -0,0 +1,55 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from flectra import api, models, fields + + +class StockAgeingWizard(models.TransientModel): + + _name = 'stock.ageing.wizard' + _description = 'Wizard that opens the stock ageing' + + company_id = fields.Many2one('res.company', string="Company", + default=lambda self: self.env.user.company_id) + branch_id = fields.Many2one('res.branch', string="Branch", + default=lambda self: + self.env.user.default_branch_id) + warehouse_ids = fields.Many2many("stock.warehouse", string="Warehouse") + location_ids = fields.Many2many("stock.location", string='Location', + domain="[('usage', '=', 'internal')]") + product_category_ids = fields.Many2many("product.category", + string="Product Category") + product_ids = fields.Many2many('product.product', string='Product', + domain="[('type', '=', 'product')]") + period_length = fields.Integer(string='Period Length (days)', default=30) + date = fields.Datetime(string="Date", + help="Choose a date to get the inventory ageing " + "report", + default=fields.Datetime.now()) + + @api.multi + def print_report(self): + """ + To get the Stock Ageing report and print the report + @return : return stock ageing report + """ + datas = {'ids': self._context.get('active_ids', [])} + res = self.read( + ['company_id', 'branch_id', 'warehouse_ids', 'location_ids', + 'product_category_ids', 'product_ids', + 'period_length', 'date']) + for ageing_dict in res: + res = res and res[0] or {} + res['company_id'] = ageing_dict['company_id'] and\ + ageing_dict['company_id'][0] or False + res['branch_id'] = ageing_dict['branch_id'] and \ + ageing_dict['branch_id'][0] or False + res['warehouse_id'] = ageing_dict['warehouse_ids'] + res['location_id'] = ageing_dict['location_ids'] + res['product_category_id'] = ageing_dict['product_category_ids'] + res['product_id'] = ageing_dict['product_ids'] + res['period_length'] = ageing_dict['period_length'] or False + res['date'] = ageing_dict['date'] or False + datas['form'] = res + return self.env.ref( + 'stock_ageing_report.action_stock_ageing_report' + '').report_action(self, data=datas) diff --git a/addons/stock_ageing_report/wizard/stock_ageing_wizard_view.xml b/addons/stock_ageing_report/wizard/stock_ageing_wizard_view.xml new file mode 100644 index 00000000..5da2fe1d --- /dev/null +++ b/addons/stock_ageing_report/wizard/stock_ageing_wizard_view.xml @@ -0,0 +1,50 @@ + + + + + Stock Ageing Wizard Form + stock.ageing.wizard + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + Stock Ageing Report + stock.ageing.wizard + form + form + {'company_id': context.get('company_id', False), 'warehouse_id': + context.get('warehouse_ids', False), 'location_id': context.get('location_ids', False), + 'product_category_id': context.get('product_category_ids', False), 'product_id': + context.get('product_ids', False), 'period_length': context.get('period_length', False), 'date': + context.get('date', False)} + + new + + + + +