Remove modules pos_no_product_template_menu and sale_purchase_no_product_template_menu
We won't port those modules to v14
This commit is contained in:
@@ -1,28 +0,0 @@
|
|||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
{
|
|
||||||
'name': 'POS No Product Template Menu',
|
|
||||||
'version': '12.0.1.0.0',
|
|
||||||
'category': 'Point of sale',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'summary': "Replace product.template menu entries by product.product menu",
|
|
||||||
'description': """
|
|
||||||
POS No Product Template
|
|
||||||
=======================
|
|
||||||
|
|
||||||
This module replaces the menu entry for product.template by menu entries
|
|
||||||
for product.product in the *Point Of Sale > Product* menu.
|
|
||||||
|
|
||||||
This module also switches to the tree view by default
|
|
||||||
for Product menu entries, instead of the kanban view.
|
|
||||||
|
|
||||||
This module has been written by David Béal
|
|
||||||
from Akretion <david.beal@akretion.com>.
|
|
||||||
""",
|
|
||||||
'author': 'Akretion',
|
|
||||||
'website': 'http://www.akretion.com',
|
|
||||||
'depends': ['point_of_sale', 'sale_purchase_no_product_template_menu'],
|
|
||||||
'auto_install': True,
|
|
||||||
'data': ['pos_view.xml'],
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
|
|
||||||
<record id="product_product_action_pos" model="ir.actions.act_window">
|
|
||||||
<field name="name">Products</field>
|
|
||||||
<field name="res_model">product.product</field>
|
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
|
||||||
<field name="context">{'default_available_in_pos': True, 'search_default_filter_to_availabe_pos': 1}</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="point_of_sale.menu_pos_products" model="ir.ui.menu">
|
|
||||||
<field name="action" ref="product_product_action_pos"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</odoo>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Copyright 2015-2019 Akretion France (http://www.akretion.com/)
|
|
||||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
{
|
|
||||||
'name': 'Sale Purchase No Product Template Menu',
|
|
||||||
'version': '12.0.1.0.0',
|
|
||||||
'category': 'Sale and Purchase',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'summary': "Replace product.template menu entries by product.product menu entries",
|
|
||||||
'description': """
|
|
||||||
Sale Purchase No Product Template
|
|
||||||
=================================
|
|
||||||
|
|
||||||
This module replaces the menu entries for product.template by menu entries for product.product in the *Sales* and *Purchases* menu entries. With this module, the only menu entry for product.template is in the menu *Sales > Configuration > Product Categories and Attributes*.
|
|
||||||
|
|
||||||
This module also switches to the tree view by default for Product menu entries, instead of the kanban view.
|
|
||||||
|
|
||||||
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
|
||||||
""",
|
|
||||||
'author': 'Akretion',
|
|
||||||
'website': 'http://www.akretion.com',
|
|
||||||
'depends': [
|
|
||||||
'purchase',
|
|
||||||
'sale',
|
|
||||||
],
|
|
||||||
'data': ['view.xml'],
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Translation of Odoo Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * sale_purchase_no_product_template_menu
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Odoo Server 8.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2016-05-30 15:27+0000\n"
|
|
||||||
"PO-Revision-Date: 2016-05-30 15:27+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: sale_purchase_no_product_template_menu
|
|
||||||
#: model:ir.ui.menu,name:sale_purchase_no_product_template_menu.sale_config_product_template_menu
|
|
||||||
msgid "Product Templates"
|
|
||||||
msgstr "Modèles d'article"
|
|
||||||
|
|
||||||
#. module: sale_purchase_no_product_template_menu
|
|
||||||
#: model:ir.actions.act_window,name:sale_purchase_no_product_template_menu.product_product_action_puchased
|
|
||||||
#: model:ir.actions.act_window,name:sale_purchase_no_product_template_menu.product_product_action_sell
|
|
||||||
msgid "Products"
|
|
||||||
msgstr "Articles"
|
|
||||||
|
|
||||||
#. module: sale_purchase_no_product_template_menu
|
|
||||||
#: view:product.product:sale_purchase_no_product_template_menu.product_normal_form_view
|
|
||||||
msgid "{'invisible': 1, 'required': 0}"
|
|
||||||
msgstr "{'invisible': 1, 'required': 0}"
|
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Translation of Odoo Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * sale_purchase_no_product_template_menu
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Odoo Server 8.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2016-05-30 15:27+0000\n"
|
|
||||||
"PO-Revision-Date: 2016-05-30 15:27+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: sale_purchase_no_product_template_menu
|
|
||||||
#: model:ir.ui.menu,name:sale_purchase_no_product_template_menu.sale_config_product_template_menu
|
|
||||||
msgid "Product Templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: sale_purchase_no_product_template_menu
|
|
||||||
#: model:ir.actions.act_window,name:sale_purchase_no_product_template_menu.product_product_action_puchased
|
|
||||||
#: model:ir.actions.act_window,name:sale_purchase_no_product_template_menu.product_product_action_sell
|
|
||||||
msgid "Products"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: sale_purchase_no_product_template_menu
|
|
||||||
#: view:product.product:sale_purchase_no_product_template_menu.product_normal_form_view
|
|
||||||
msgid "{'invisible': 1, 'required': 0}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright 2015-2019 Akretion France (http://www.akretion.com/)
|
|
||||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
-->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
|
|
||||||
<!-- PURCHASE -->
|
|
||||||
<record id="product_product_action_purchased" model="ir.actions.act_window">
|
|
||||||
<field name="name">Products</field>
|
|
||||||
<field name="res_model">product.product</field>
|
|
||||||
<field name="view_mode">tree,form,kanban</field>
|
|
||||||
<field name="context">{'search_default_filter_to_purchase': 1}</field>
|
|
||||||
<field name="search_view_id" eval="False"/> <!-- Force empty -->
|
|
||||||
<field name="view_id" eval="False"/> <!-- Force empty -->
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="purchase.menu_procurement_partner_contact_form" model="ir.ui.menu">
|
|
||||||
<field name="action" ref="product_product_action_purchased"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- SALE -->
|
|
||||||
<!-- I'd prefer to inherit product.product_normal_action_sell and
|
|
||||||
change the "name" field, but it doesn't work with translation,
|
|
||||||
so I redefine a new menu entry -->
|
|
||||||
<record id="product_product_action_sell" model="ir.actions.act_window">
|
|
||||||
<field name="name">Products</field>
|
|
||||||
<field name="res_model">product.product</field>
|
|
||||||
<field name="view_mode">tree,form,kanban</field>
|
|
||||||
<field name="context">{'search_default_filter_to_sell': 1}</field>
|
|
||||||
<field name="search_view_id" eval="False"/>
|
|
||||||
<field name="view_id" ref="product.product_product_tree_view"/>
|
|
||||||
<field name="search_view_id" ref="product.product_search_form_view"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- To keep good translations, we re-use the product.template menu
|
|
||||||
entry and link it to product product -->
|
|
||||||
<record id="sale.menu_product_template_action" model="ir.ui.menu">
|
|
||||||
<!-- related action is "product.product_template_action" -->
|
|
||||||
<field name="action" ref="product_product_action_sell"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="product.product_template_action" model="ir.actions.act_window">
|
|
||||||
<field name="name">Product Templates</field> <!-- native value is "Products" -->
|
|
||||||
<field name="view_mode">tree,form,kanban</field>
|
|
||||||
<field name="view_id" eval="False"/>
|
|
||||||
<field name="context">{}</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Create a product template menu entry in configuration -->
|
|
||||||
<menuitem id="sale_config_product_template_menu" action="product.product_template_action"
|
|
||||||
parent="sale.prod_config_main"/>
|
|
||||||
|
|
||||||
|
|
||||||
<record id="product.product_normal_action_sell" model="ir.actions.act_window">
|
|
||||||
<field name="view_mode">tree,form,kanban</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
|
|
||||||
</odoo>
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
from . import models
|
||||||
from . import wizard
|
from . import wizard
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Stock Valuation XLSX',
|
'name': 'Stock Valuation XLSX',
|
||||||
'version': '12.0.1.0.0',
|
'version': '14.0.1.0.0',
|
||||||
'category': 'Tools',
|
'category': 'Tools',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'summary': 'Generate XLSX reports for past or present stock levels',
|
'summary': 'Generate XLSX reports for past or present stock levels',
|
||||||
@@ -37,8 +37,11 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
|
|||||||
'website': 'http://www.akretion.com',
|
'website': 'http://www.akretion.com',
|
||||||
'depends': ['stock_account'],
|
'depends': ['stock_account'],
|
||||||
'data': [
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
'wizard/stock_valuation_xlsx_view.xml',
|
'wizard/stock_valuation_xlsx_view.xml',
|
||||||
|
'wizard/stock_variation_xlsx_view.xml',
|
||||||
'views/stock_inventory.xml',
|
'views/stock_inventory.xml',
|
||||||
|
'views/stock_expiry_depreciation_rule.xml',
|
||||||
],
|
],
|
||||||
'installable': False,
|
'installable': True,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<button name="action_validate" position="after">
|
<button name="action_validate" position="after">
|
||||||
<button name="%(stock_valuation_xlsx_action)d" type="action"
|
<button name="%(stock_valuation_xlsx_action)d" type="action"
|
||||||
states="done" string="XLSX Valuation Report"
|
states="done" string="XLSX Valuation Report"
|
||||||
context="{'default_source': 'inventory', 'default_inventory_id': active_id, 'default_location_id': location_id}"/>
|
context="{'default_source': 'inventory', 'default_inventory_id': active_id}"/>
|
||||||
</button>
|
</button>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
from . import stock_valuation_xlsx
|
from . import stock_valuation_xlsx
|
||||||
|
from . import stock_variation_xlsx
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
from odoo.tools import float_is_zero, float_round
|
from odoo.tools import float_is_zero, float_round
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -18,7 +19,7 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
_name = 'stock.valuation.xlsx'
|
_name = 'stock.valuation.xlsx'
|
||||||
_description = 'Generate XLSX report for stock valuation'
|
_description = 'Generate XLSX report for stock valuation'
|
||||||
|
|
||||||
export_file = fields.Binary(string='XLSX Report', readonly=True)
|
export_file = fields.Binary(string='XLSX Report', readonly=True, attachment=True)
|
||||||
export_filename = fields.Char(readonly=True)
|
export_filename = fields.Char(readonly=True)
|
||||||
# I don't use ir.actions.url on v12, because it renders
|
# I don't use ir.actions.url on v12, because it renders
|
||||||
# the wizard unusable after the first report generation, which creates
|
# the wizard unusable after the first report generation, which creates
|
||||||
@@ -38,8 +39,10 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
help="The childen locations of the selected locations will "
|
help="The childen locations of the selected locations will "
|
||||||
u"be taken in the valuation.")
|
u"be taken in the valuation.")
|
||||||
categ_ids = fields.Many2many(
|
categ_ids = fields.Many2many(
|
||||||
'product.category', string='Product Categories',
|
'product.category', string='Product Category Filter',
|
||||||
states={'done': [('readonly', True)]})
|
help="Leave this field empty to have a stock valuation for all your products.",
|
||||||
|
states={'done': [('readonly', True)]},
|
||||||
|
)
|
||||||
source = fields.Selection([
|
source = fields.Selection([
|
||||||
('inventory', 'Physical Inventory'),
|
('inventory', 'Physical Inventory'),
|
||||||
('stock', 'Stock Levels'),
|
('stock', 'Stock Levels'),
|
||||||
@@ -59,17 +62,33 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
categ_subtotal = fields.Boolean(
|
categ_subtotal = fields.Boolean(
|
||||||
string='Subtotals per Categories', default=True,
|
string='Subtotals per Categories', default=True,
|
||||||
states={'done': [('readonly', True)]},
|
states={'done': [('readonly', True)]},
|
||||||
help="Show a subtotal per product category")
|
help="Show a subtotal per product category.")
|
||||||
standard_price_date = fields.Selection([
|
standard_price_date = fields.Selection([
|
||||||
('past', 'Past Date or Inventory Date'),
|
('past', 'Past Date or Inventory Date'),
|
||||||
('present', 'Current'),
|
('present', 'Current'),
|
||||||
], default='past', string='Cost Price Date',
|
], default='past', string='Cost Price Date',
|
||||||
states={'done': [('readonly', True)]})
|
states={'done': [('readonly', True)]})
|
||||||
|
# I can't put a compute field for has_expiry_date
|
||||||
|
# because I want to have the value when the wizard is started,
|
||||||
|
# and not wait until run
|
||||||
|
has_expiry_date = fields.Boolean(
|
||||||
|
default=lambda self: self._default_has_expiry_date(), readonly=True)
|
||||||
|
apply_depreciation = fields.Boolean(
|
||||||
|
string='Apply Depreciation Rules', default=True,
|
||||||
|
states={'done': [('readonly', True)]})
|
||||||
split_by_lot = fields.Boolean(
|
split_by_lot = fields.Boolean(
|
||||||
string='Display Lots', states={'done': [('readonly', True)]})
|
string='Display Lots', states={'done': [('readonly', True)]})
|
||||||
split_by_location = fields.Boolean(
|
split_by_location = fields.Boolean(
|
||||||
string='Display Stock Locations', states={'done': [('readonly', True)]})
|
string='Display Stock Locations', states={'done': [('readonly', True)]})
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _default_has_expiry_date(self):
|
||||||
|
splo = self.env['stock.production.lot']
|
||||||
|
has_expiry_date = False
|
||||||
|
if hasattr(splo, 'expiry_date'):
|
||||||
|
has_expiry_date = True
|
||||||
|
return has_expiry_date
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _default_location(self):
|
def _default_location(self):
|
||||||
wh = self.env.ref('stock.warehouse0')
|
wh = self.env.ref('stock.warehouse0')
|
||||||
@@ -123,6 +142,17 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
def _prepare_product_fields(self):
|
def _prepare_product_fields(self):
|
||||||
return ['uom_id', 'name', 'default_code', 'categ_id']
|
return ['uom_id', 'name', 'default_code', 'categ_id']
|
||||||
|
|
||||||
|
def _prepare_expiry_depreciation_rules(self, company_id, past_date):
|
||||||
|
rules = self.env['stock.expiry.depreciation.rule'].search_read([('company_id', '=', company_id)], ['start_limit_days', 'ratio'], order='start_limit_days desc')
|
||||||
|
if past_date:
|
||||||
|
date_dt = past_date
|
||||||
|
else:
|
||||||
|
date_dt = fields.Date.context_today(self)
|
||||||
|
for rule in rules:
|
||||||
|
rule['start_date'] = date_dt - relativedelta(days=rule['start_limit_days'])
|
||||||
|
logger.debug('depreciation_rules=%s', rules)
|
||||||
|
return rules
|
||||||
|
|
||||||
def compute_product_data(
|
def compute_product_data(
|
||||||
self, company_id, in_stock_product_ids, standard_price_past_date=False):
|
self, company_id, in_stock_product_ids, standard_price_past_date=False):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -156,38 +186,56 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
logger.debug('End compute_product_data')
|
logger.debug('End compute_product_data')
|
||||||
return product_id2data
|
return product_id2data
|
||||||
|
|
||||||
def id2name(self, product_ids):
|
@api.model
|
||||||
logger.debug('Start id2name')
|
def product_categ_id2name(self, categories):
|
||||||
pco = self.env['product.category']
|
pco = self.env['product.category']
|
||||||
splo = self.env['stock.production.lot']
|
|
||||||
slo = self.env['stock.location'].with_context(active_test=False)
|
|
||||||
puo = self.env['uom.uom'].with_context(active_test=False)
|
|
||||||
categ_id2name = {}
|
categ_id2name = {}
|
||||||
categ_domain = []
|
categ_domain = []
|
||||||
if self.categ_ids:
|
if categories:
|
||||||
categ_domain = [('id', 'child_of', self.categ_ids.ids)]
|
categ_domain = [('id', 'child_of', categories.ids)]
|
||||||
for categ in pco.search_read(categ_domain, ['display_name']):
|
for categ in pco.search_read(categ_domain, ['display_name']):
|
||||||
categ_id2name[categ['id']] = categ['display_name']
|
categ_id2name[categ['id']] = categ['display_name']
|
||||||
|
return categ_id2name
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def uom_id2name(self):
|
||||||
|
puo = self.env['uom.uom'].with_context(active_test=False)
|
||||||
uom_id2name = {}
|
uom_id2name = {}
|
||||||
uoms = puo.search_read([], ['name'])
|
uoms = puo.search_read([], ['name'])
|
||||||
for uom in uoms:
|
for uom in uoms:
|
||||||
uom_id2name[uom['id']] = uom['name']
|
uom_id2name[uom['id']] = uom['name']
|
||||||
|
return uom_id2name
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def prodlot_id2data(self, product_ids, has_expiry_date, depreciation_rules):
|
||||||
|
splo = self.env['stock.production.lot']
|
||||||
lot_id2data = {}
|
lot_id2data = {}
|
||||||
lot_fields = ['name']
|
lot_fields = ['name']
|
||||||
if hasattr(splo, 'expiry_date'):
|
if has_expiry_date:
|
||||||
lot_fields.append('expiry_date')
|
lot_fields.append('expiry_date')
|
||||||
|
|
||||||
lots = splo.search_read(
|
lots = splo.search_read(
|
||||||
[('product_id', 'in', product_ids)], lot_fields)
|
[('product_id', 'in', product_ids)], lot_fields)
|
||||||
for lot in lots:
|
for lot in lots:
|
||||||
lot_id2data[lot['id']] = lot
|
lot_id2data[lot['id']] = lot
|
||||||
|
lot_id2data[lot['id']]['depreciation_ratio'] = 0
|
||||||
|
if depreciation_rules and lot.get('expiry_date'):
|
||||||
|
expiry_date = lot['expiry_date']
|
||||||
|
for rule in depreciation_rules:
|
||||||
|
if expiry_date <= rule['start_date']:
|
||||||
|
lot_id2data[lot['id']]['depreciation_ratio'] = rule['ratio'] / 100.0
|
||||||
|
break
|
||||||
|
return lot_id2data
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def stock_location_id2name(self, location):
|
||||||
|
slo = self.env['stock.location'].with_context(active_test=False)
|
||||||
loc_id2name = {}
|
loc_id2name = {}
|
||||||
locs = slo.search_read(
|
locs = slo.search_read(
|
||||||
[('id', 'child_of', self.location_id.id)], ['display_name'])
|
[('id', 'child_of', self.location_id.id)], ['display_name'])
|
||||||
for loc in locs:
|
for loc in locs:
|
||||||
loc_id2name[loc['id']] = loc['display_name']
|
loc_id2name[loc['id']] = loc['display_name']
|
||||||
logger.debug('End id2name')
|
return loc_id2name
|
||||||
return categ_id2name, uom_id2name, lot_id2data, loc_id2name
|
|
||||||
|
|
||||||
def compute_data_from_inventory(self, product_ids, prec_qty):
|
def compute_data_from_inventory(self, product_ids, prec_qty):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -275,7 +323,7 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
def stringify_and_sort_result(
|
def stringify_and_sort_result(
|
||||||
self, product_ids, product_id2data, data,
|
self, product_ids, product_id2data, data,
|
||||||
prec_qty, prec_price, prec_cur_rounding, categ_id2name,
|
prec_qty, prec_price, prec_cur_rounding, categ_id2name,
|
||||||
uom_id2name, lot_id2data, loc_id2name):
|
uom_id2name, lot_id2data, loc_id2name, apply_depreciation):
|
||||||
logger.debug('Start stringify_and_sort_result')
|
logger.debug('Start stringify_and_sort_result')
|
||||||
res = []
|
res = []
|
||||||
for l in data:
|
for l in data:
|
||||||
@@ -284,17 +332,27 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
standard_price = float_round(
|
standard_price = float_round(
|
||||||
product_id2data[product_id]['standard_price'],
|
product_id2data[product_id]['standard_price'],
|
||||||
precision_digits=prec_price)
|
precision_digits=prec_price)
|
||||||
subtotal = float_round(
|
subtotal_before_depreciation = float_round(
|
||||||
standard_price * qty, precision_rounding=prec_cur_rounding)
|
standard_price * qty, precision_rounding=prec_cur_rounding)
|
||||||
|
depreciation_ratio = 0
|
||||||
|
if apply_depreciation and l['lot_id']:
|
||||||
|
depreciation_ratio = lot_id2data[l['lot_id']].get('depreciation_ratio', 0)
|
||||||
|
subtotal = float_round(
|
||||||
|
subtotal_before_depreciation * (1 - depreciation_ratio),
|
||||||
|
precision_rounding=prec_cur_rounding)
|
||||||
|
else:
|
||||||
|
subtotal = subtotal_before_depreciation
|
||||||
res.append(dict(
|
res.append(dict(
|
||||||
product_id2data[product_id],
|
product_id2data[product_id],
|
||||||
product_name=product_id2data[product_id]['name'],
|
product_name=product_id2data[product_id]['name'],
|
||||||
loc_name=l['location_id'] and loc_id2name[l['location_id']] or '',
|
loc_name=l['location_id'] and loc_id2name[l['location_id']] or '',
|
||||||
lot_name=l['lot_id'] and lot_id2data[l['lot_id']]['name'] or '',
|
lot_name=l['lot_id'] and lot_id2data[l['lot_id']]['name'] or '',
|
||||||
expiry_date=l['lot_id'] and lot_id2data[l['lot_id']].get('expiry_date'),
|
expiry_date=l['lot_id'] and lot_id2data[l['lot_id']].get('expiry_date'),
|
||||||
|
depreciation_ratio=depreciation_ratio,
|
||||||
qty=qty,
|
qty=qty,
|
||||||
uom_name=uom_id2name[product_id2data[product_id]['uom_id']],
|
uom_name=uom_id2name[product_id2data[product_id]['uom_id']],
|
||||||
standard_price=standard_price,
|
standard_price=standard_price,
|
||||||
|
subtotal_before_depreciation=subtotal_before_depreciation,
|
||||||
subtotal=subtotal,
|
subtotal=subtotal,
|
||||||
categ_name=categ_id2name[product_id2data[product_id]['categ_id']],
|
categ_name=categ_id2name[product_id2data[product_id]['categ_id']],
|
||||||
))
|
))
|
||||||
@@ -313,6 +371,12 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
prec_cur_rounding = company.currency_id.rounding
|
prec_cur_rounding = company.currency_id.rounding
|
||||||
self._check_config(company_id)
|
self._check_config(company_id)
|
||||||
|
|
||||||
|
apply_depreciation = self.apply_depreciation
|
||||||
|
if (
|
||||||
|
(self.source == 'stock' and self.stock_date_type == 'past') or
|
||||||
|
not self.split_by_lot or
|
||||||
|
not self.has_expiry_date):
|
||||||
|
apply_depreciation = False
|
||||||
product_ids = self.get_product_ids()
|
product_ids = self.get_product_ids()
|
||||||
if not product_ids:
|
if not product_ids:
|
||||||
raise UserError(_("There are no products to analyse."))
|
raise UserError(_("There are no products to analyse."))
|
||||||
@@ -335,15 +399,25 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
standard_price_past_date = past_date
|
standard_price_past_date = past_date
|
||||||
if not (self.source == 'stock' and self.stock_date_type == 'present') and self.standard_price_date == 'present':
|
if not (self.source == 'stock' and self.stock_date_type == 'present') and self.standard_price_date == 'present':
|
||||||
standard_price_past_date = False
|
standard_price_past_date = False
|
||||||
|
depreciation_rules = []
|
||||||
|
if apply_depreciation:
|
||||||
|
depreciation_rules = self._prepare_expiry_depreciation_rules(company_id, past_date)
|
||||||
|
if not depreciation_rules:
|
||||||
|
raise UserError(_(
|
||||||
|
"The are not stock depreciation rule for company '%s'.")
|
||||||
|
% company.display_name)
|
||||||
in_stock_product_ids = list(in_stock_products.keys())
|
in_stock_product_ids = list(in_stock_products.keys())
|
||||||
product_id2data = self.compute_product_data(
|
product_id2data = self.compute_product_data(
|
||||||
company_id, in_stock_product_ids,
|
company_id, in_stock_product_ids,
|
||||||
standard_price_past_date=standard_price_past_date)
|
standard_price_past_date=standard_price_past_date)
|
||||||
data_res = self.group_result(data, split_by_lot, split_by_location)
|
data_res = self.group_result(data, split_by_lot, split_by_location)
|
||||||
categ_id2name, uom_id2name, lot_id2data, loc_id2name = self.id2name(product_ids)
|
categ_id2name = self.product_categ_id2name(self.categ_ids)
|
||||||
|
uom_id2name = self.uom_id2name()
|
||||||
|
lot_id2data = self.prodlot_id2data(in_stock_product_ids, self.has_expiry_date, depreciation_rules)
|
||||||
|
loc_id2name = self.stock_location_id2name(self.location_id)
|
||||||
res = self.stringify_and_sort_result(
|
res = self.stringify_and_sort_result(
|
||||||
product_ids, product_id2data, data_res, prec_qty, prec_price, prec_cur_rounding,
|
product_ids, product_id2data, data_res, prec_qty, prec_price, prec_cur_rounding,
|
||||||
categ_id2name, uom_id2name, lot_id2data, loc_id2name)
|
categ_id2name, uom_id2name, lot_id2data, loc_id2name, apply_depreciation)
|
||||||
|
|
||||||
logger.debug('Start create XLSX workbook')
|
logger.debug('Start create XLSX workbook')
|
||||||
file_data = BytesIO()
|
file_data = BytesIO()
|
||||||
@@ -356,12 +430,15 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
if not split_by_lot:
|
if not split_by_lot:
|
||||||
cols.pop('lot_name', None)
|
cols.pop('lot_name', None)
|
||||||
cols.pop('expiry_date', None)
|
cols.pop('expiry_date', None)
|
||||||
if not hasattr(splo, 'expiry_date'):
|
if not self.has_expiry_date:
|
||||||
cols.pop('expiry_date', None)
|
cols.pop('expiry_date', None)
|
||||||
if not split_by_location:
|
if not split_by_location:
|
||||||
cols.pop('loc_name', None)
|
cols.pop('loc_name', None)
|
||||||
if not categ_subtotal:
|
if not categ_subtotal:
|
||||||
cols.pop('categ_subtotal', None)
|
cols.pop('categ_subtotal', None)
|
||||||
|
if not apply_depreciation:
|
||||||
|
cols.pop('depreciation_ratio', None)
|
||||||
|
cols.pop('subtotal_before_depreciation', None)
|
||||||
|
|
||||||
j = 0
|
j = 0
|
||||||
for col, col_vals in sorted(cols.items(), key=lambda x: x[1]['sequence']):
|
for col, col_vals in sorted(cols.items(), key=lambda x: x[1]['sequence']):
|
||||||
@@ -417,6 +494,9 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
letter_qty = cols['qty']['pos_letter']
|
letter_qty = cols['qty']['pos_letter']
|
||||||
letter_price = cols['standard_price']['pos_letter']
|
letter_price = cols['standard_price']['pos_letter']
|
||||||
letter_subtotal = cols['subtotal']['pos_letter']
|
letter_subtotal = cols['subtotal']['pos_letter']
|
||||||
|
if apply_depreciation:
|
||||||
|
letter_subtotal_before_depreciation = cols['subtotal_before_depreciation']['pos_letter']
|
||||||
|
letter_depreciation_ratio = cols['depreciation_ratio']['pos_letter']
|
||||||
crow = 0
|
crow = 0
|
||||||
lines = res
|
lines = res
|
||||||
for categ_id in categ_ids:
|
for categ_id in categ_ids:
|
||||||
@@ -432,12 +512,20 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
total += l['subtotal']
|
total += l['subtotal']
|
||||||
ctotal += l['subtotal']
|
ctotal += l['subtotal']
|
||||||
categ_has_line = True
|
categ_has_line = True
|
||||||
subtotal_formula = '=%s%d*%s%d' % (letter_qty, i + 1, letter_price, i + 1)
|
qty_by_price_formula = '=%s%d*%s%d' % (letter_qty, i + 1, letter_price, i + 1)
|
||||||
|
if apply_depreciation:
|
||||||
|
sheet.write_formula(i, cols['subtotal_before_depreciation']['pos'], qty_by_price_formula, styles['regular_currency'], l['subtotal_before_depreciation'])
|
||||||
|
subtotal_formula = '=%s%d*(1 - %s%d)' % (letter_subtotal_before_depreciation, i + 1, letter_depreciation_ratio, i + 1)
|
||||||
|
else:
|
||||||
|
subtotal_formula = qty_by_price_formula
|
||||||
sheet.write_formula(i, cols['subtotal']['pos'], subtotal_formula, styles['regular_currency'], l['subtotal'])
|
sheet.write_formula(i, cols['subtotal']['pos'], subtotal_formula, styles['regular_currency'], l['subtotal'])
|
||||||
for col_name, col in cols.items():
|
for col_name, col in cols.items():
|
||||||
if not col.get('formula'):
|
if not col.get('formula'):
|
||||||
if col.get('type') == 'date' and l[col_name]:
|
if col.get('type') == 'date':
|
||||||
l[col_name] = fields.Date.from_string(l[col_name])
|
if l[col_name]:
|
||||||
|
l[col_name] = fields.Date.from_string(l[col_name])
|
||||||
|
else:
|
||||||
|
l[col_name] = '' # to avoid display of 31/12/1899
|
||||||
sheet.write(i, col['pos'], l[col_name], styles[col['style']])
|
sheet.write(i, col['pos'], l[col_name], styles[col['style']])
|
||||||
if categ_subtotal:
|
if categ_subtotal:
|
||||||
if categ_has_line:
|
if categ_has_line:
|
||||||
@@ -503,6 +591,7 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
'regular_date': workbook.add_format({'num_format': 'dd/mm/yyyy'}),
|
'regular_date': workbook.add_format({'num_format': 'dd/mm/yyyy'}),
|
||||||
'regular_currency': workbook.add_format({'num_format': currency_num_format}),
|
'regular_currency': workbook.add_format({'num_format': currency_num_format}),
|
||||||
'regular_price_currency': workbook.add_format({'num_format': price_currency_num_format}),
|
'regular_price_currency': workbook.add_format({'num_format': price_currency_num_format}),
|
||||||
|
'regular_int_percent': workbook.add_format({'num_format': u'0.%'}),
|
||||||
'regular': workbook.add_format({}),
|
'regular': workbook.add_format({}),
|
||||||
'regular_small': workbook.add_format({'font_size': regular_font_size - 2}),
|
'regular_small': workbook.add_format({'font_size': regular_font_size - 2}),
|
||||||
'categ_title': workbook.add_format({
|
'categ_title': workbook.add_format({
|
||||||
@@ -527,8 +616,10 @@ class StockValuationXlsx(models.TransientModel):
|
|||||||
'qty': {'width': 8, 'style': 'regular', 'sequence': 60, 'title': _('Qty')},
|
'qty': {'width': 8, 'style': 'regular', 'sequence': 60, 'title': _('Qty')},
|
||||||
'uom_name': {'width': 5, 'style': 'regular_small', 'sequence': 70, 'title': _('UoM')},
|
'uom_name': {'width': 5, 'style': 'regular_small', 'sequence': 70, 'title': _('UoM')},
|
||||||
'standard_price': {'width': 14, 'style': 'regular_price_currency', 'sequence': 80, 'title': _('Cost Price')},
|
'standard_price': {'width': 14, 'style': 'regular_price_currency', 'sequence': 80, 'title': _('Cost Price')},
|
||||||
'subtotal': {'width': 16, 'style': 'regular_currency', 'sequence': 90, 'title': _('Sub-total'), 'formula': True},
|
'subtotal_before_depreciation': {'width': 16, 'style': 'regular_currency', 'sequence': 90, 'title': _('Sub-total'), 'formula': True},
|
||||||
'categ_subtotal': {'width': 16, 'style': 'regular_currency', 'sequence': 100, 'title': _('Categ Sub-total'), 'formula': True},
|
'depreciation_ratio': {'width': 10, 'style': 'regular_int_percent', 'sequence': 100, 'title': _('Depreciation')},
|
||||||
'categ_name': {'width': 40, 'style': 'regular_small', 'sequence': 110, 'title': _('Product Category')},
|
'subtotal': {'width': 16, 'style': 'regular_currency', 'sequence': 110, 'title': _('Sub-total'), 'formula': True},
|
||||||
|
'categ_subtotal': {'width': 16, 'style': 'regular_currency', 'sequence': 120, 'title': _('Categ Sub-total'), 'formula': True},
|
||||||
|
'categ_name': {'width': 40, 'style': 'regular_small', 'sequence': 130, 'title': _('Product Category')},
|
||||||
}
|
}
|
||||||
return cols
|
return cols
|
||||||
|
|||||||
@@ -27,8 +27,10 @@
|
|||||||
<field name="past_date" attrs="{'invisible': ['|', ('source', '!=', 'stock'), ('stock_date_type', '!=', 'past')], 'required': [('source', '=', 'stock'), ('stock_date_type', '=', 'past')]}"/>
|
<field name="past_date" attrs="{'invisible': ['|', ('source', '!=', 'stock'), ('stock_date_type', '!=', 'past')], 'required': [('source', '=', 'stock'), ('stock_date_type', '=', 'past')]}"/>
|
||||||
<field name="standard_price_date" attrs="{'invisible': [('source', '=', 'stock'), ('stock_date_type', '=', 'present')]}" widget="radio"/>
|
<field name="standard_price_date" attrs="{'invisible': [('source', '=', 'stock'), ('stock_date_type', '=', 'present')]}" widget="radio"/>
|
||||||
<field name="categ_subtotal" />
|
<field name="categ_subtotal" />
|
||||||
|
<field name="has_expiry_date" invisible="1"/>
|
||||||
<field name="split_by_lot" attrs="{'invisible': [('source', '=', 'stock'), ('stock_date_type', '=', 'past')]}" groups="stock.group_production_lot"/>
|
<field name="split_by_lot" attrs="{'invisible': [('source', '=', 'stock'), ('stock_date_type', '=', 'past')]}" groups="stock.group_production_lot"/>
|
||||||
<field name="split_by_location" attrs="{'invisible': [('source', '=', 'stock'), ('stock_date_type', '=', 'past')]}"/>
|
<field name="split_by_location" attrs="{'invisible': [('source', '=', 'stock'), ('stock_date_type', '=', 'past')]}"/>
|
||||||
|
<field name="apply_depreciation" groups="stock.group_production_lot" attrs="{'invisible': ['|', '|', ('split_by_lot', '=', False), ('has_expiry_date', '=', False), '&', ('source', '=', 'stock'), ('stock_date_type', '=', 'past')]}"/>
|
||||||
</group>
|
</group>
|
||||||
<group name="done" states="done" string="Result">
|
<group name="done" states="done" string="Result">
|
||||||
<field name="export_file" filename="export_filename"/>
|
<field name="export_file" filename="export_filename"/>
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
<record id="stock_account.menu_valuation" model="ir.ui.menu">
|
<record id="stock_account.menu_valuation" model="ir.ui.menu">
|
||||||
<field name="action" ref="stock_valuation_xlsx.stock_valuation_xlsx_action"/>
|
<field name="action" ref="stock_valuation_xlsx.stock_valuation_xlsx_action"/>
|
||||||
<field name="name">Stock Valuation XLSX</field>
|
<field name="name">Stock Valuation XLSX</field>
|
||||||
|
<field name="sequence">0</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
Reference in New Issue
Block a user