diff --git a/sale_budget/README.rst b/sale_budget/README.rst new file mode 100644 index 000000000..20cd13e41 --- /dev/null +++ b/sale_budget/README.rst @@ -0,0 +1,70 @@ +.. |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 + +================================ +Budgets for quotations and sales +================================ + +TODO + +Installation +============ + +To install this module, you need to: + +#. Only need to install the module + +Configuration +============= + +To configure this module, you need to: + +#. Nothing to configure + +Usage +===== + +To use this module, you need to: + +#. You can set three discount to sale order line. + +.. 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 +`_. 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 +------------ + +Maintainer +---------- + +|company_logo| + +This module is maintained by the |company|. + +To contribute to this module, please visit https://www.adhoc.com.ar. diff --git a/sale_budget/__init__.py b/sale_budget/__init__.py new file mode 100644 index 000000000..d03377692 --- /dev/null +++ b/sale_budget/__init__.py @@ -0,0 +1,5 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from . import models diff --git a/sale_budget/__manifest__.py b/sale_budget/__manifest__.py new file mode 100644 index 000000000..fc8fdc1dd --- /dev/null +++ b/sale_budget/__manifest__.py @@ -0,0 +1,45 @@ +############################################################################## +# +# Copyright (C) 2023 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 . +# +############################################################################## +{ + 'name': 'Budgets for quotations and sales', + 'version': "16.0.1.0.0", + 'category': 'Sales Management', + 'sequence': 14, + 'author': 'ADHOC SA', + 'website': 'www.adhoc.com.ar', + 'license': 'AGPL-3', + 'summary': '', + 'depends': [ + 'sale_management', + 'account_budget', + ], + 'data': [ + 'views/account_budget_post_views.xml', + 'views/crossovered_budget_lines_views.xml', + 'views/account_budget_views.xml', + 'views/sale_order_views.xml', + 'security/ir.model.access.csv', + ], + 'demo': [ + ], + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/sale_budget/data/account_budget_demo.xml b/sale_budget/data/account_budget_demo.xml new file mode 100644 index 000000000..f245b2ed8 --- /dev/null +++ b/sale_budget/data/account_budget_demo.xml @@ -0,0 +1,206 @@ + + + + + + + Budget template + + + + + + + + + + -35000 + + + + + + + 10000 + + + + + + + 10000 + + + + + + + 12000 + + + + + + + 15000 + + + + + + + 15000 + + + + + + + 15000 + + + + + + + 13000 + + + + + + + 9000 + + + + + + + 8000 + + + + + + + 15000 + + + + + + + 15000 + + + + + + + 18000 + + + + + + + + + + + + + + + + + -55000 + + + + + + + 9000 + + + + + + + 8000 + + + + + + + 10000 + + + + + + + 14000 + + + + + + + 16000 + + + + + + + 13000 + + + + + + + 10000 + + + + + + + 8000 + + + + + + + 7000 + + + + + + + 12000 + + + + + + + 17000 + + + + + + + 17000 + + + + diff --git a/sale_budget/i18n/de.po b/sale_budget/i18n/de.po new file mode 100644 index 000000000..8f08fb886 --- /dev/null +++ b/sale_budget/i18n/de.po @@ -0,0 +1,60 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_three_discounts +# +# Translators: +# Juan José Scarafía , 2020 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-01-11 13:03+0000\n" +"PO-Revision-Date: 2020-06-12 01:55+0000\n" +"Last-Translator: Juan José Scarafía , 2020\n" +"Language-Team: German (https://www.transifex.com/adhoc/teams/46451/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: sale_three_discounts +#: code:addons/sale_three_discounts/models/account_invoice_line.py:0 +#: code:addons/sale_three_discounts/models/sale_order_line.py:0 +#, python-format +msgid ", must be less or equal than 100" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount +msgid "Discount (%)" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount1 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount1 +msgid "Discount 1 (%)" +msgstr "Rabatt 1 (%)" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount2 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount2 +msgid "Discount 2 (%)" +msgstr "Rabatt 2 (%)" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount3 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount3 +msgid "Discount 3 (%)" +msgstr "Rabatt 3 (%)" + +#. module: sale_three_discounts +#: model:ir.model,name:sale_three_discounts.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model,name:sale_three_discounts.model_sale_order_line +msgid "Sales Order Line" +msgstr "Auftragsposition" diff --git a/sale_budget/i18n/es.po b/sale_budget/i18n/es.po new file mode 100644 index 000000000..d70442540 --- /dev/null +++ b/sale_budget/i18n/es.po @@ -0,0 +1,60 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_three_discounts +# +# Translators: +# Juan José Scarafía , 2022 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-04-24 16:47+0000\n" +"PO-Revision-Date: 2022-04-19 14:00+0000\n" +"Last-Translator: Juan José Scarafía , 2022\n" +"Language-Team: Spanish (https://www.transifex.com/adhoc/teams/133229/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: sale_three_discounts +#: code:addons/sale_three_discounts/models/account_invoice_line.py:0 +#: code:addons/sale_three_discounts/models/sale_order_line.py:0 +#, python-format +msgid ", must be less or equal than 100" +msgstr ", debe ser menor o igual a 100" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount +msgid "Discount (%)" +msgstr "Descuento (%)" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount1 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount1 +msgid "Discount 1 (%)" +msgstr "Descuento 1 (%)" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount2 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount2 +msgid "Discount 2 (%)" +msgstr "Descuento 2 (%)" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount3 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount3 +msgid "Discount 3 (%)" +msgstr "Descuento 3 (%)" + +#. module: sale_three_discounts +#: model:ir.model,name:sale_three_discounts.model_account_move_line +msgid "Journal Item" +msgstr "Apunte contable" + +#. module: sale_three_discounts +#: model:ir.model,name:sale_three_discounts.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de pedido de venta" diff --git a/sale_budget/i18n/ru.po b/sale_budget/i18n/ru.po new file mode 100644 index 000000000..4d2e3c949 --- /dev/null +++ b/sale_budget/i18n/ru.po @@ -0,0 +1,60 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_three_discounts +# +# Translators: +# Ekaterina , 2020 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-01-11 13:03+0000\n" +"PO-Revision-Date: 2020-06-12 01:55+0000\n" +"Last-Translator: Ekaterina , 2020\n" +"Language-Team: Russian (https://www.transifex.com/adhoc/teams/46451/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#. module: sale_three_discounts +#: code:addons/sale_three_discounts/models/account_invoice_line.py:0 +#: code:addons/sale_three_discounts/models/sale_order_line.py:0 +#, python-format +msgid ", must be less or equal than 100" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount +msgid "Discount (%)" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount1 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount1 +msgid "Discount 1 (%)" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount2 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount2 +msgid "Discount 2 (%)" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model.fields,field_description:sale_three_discounts.field_account_move_line__discount3 +#: model:ir.model.fields,field_description:sale_three_discounts.field_sale_order_line__discount3 +msgid "Discount 3 (%)" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model,name:sale_three_discounts.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: sale_three_discounts +#: model:ir.model,name:sale_three_discounts.model_sale_order_line +msgid "Sales Order Line" +msgstr "Заказ на Продажу" diff --git a/sale_budget/models/__init__.py b/sale_budget/models/__init__.py new file mode 100644 index 000000000..300c5b8d9 --- /dev/null +++ b/sale_budget/models/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## + +from . import account_budget_post +from . import account_budget +from . import crossovered_budget_line_detail +from . import crossovered_budget_lines +from . import sale_order diff --git a/sale_budget/models/account_budget.py b/sale_budget/models/account_budget.py new file mode 100644 index 000000000..b92118d4b --- /dev/null +++ b/sale_budget/models/account_budget.py @@ -0,0 +1,21 @@ +from odoo import models + + +class AccountBudget(models.Model): + _inherit = "crossovered.budget" + + def copy_and_link_to_so(self): + import pdb; pdb.set_trace() + sale = self._context.get('active_model').browse()._context.get('active_id') + if not sale or sale._name != 'sale.order': + # TODO devolver un raise + return + if not sale.analytic_account_id: + sale.analytic_account_id = sale.analytic_account_id.create({ + 'name': self.name, + 'partner_id': self.partner_invoice_id.id, + }) + self.copy() + for line in self.crossovered_budget_line: + line.analytic_account_id = sale.analytic_account_id.id + return diff --git a/sale_budget/models/account_budget_post.py b/sale_budget/models/account_budget_post.py new file mode 100644 index 000000000..0410da353 --- /dev/null +++ b/sale_budget/models/account_budget_post.py @@ -0,0 +1,21 @@ +from odoo import fields, models + + +class AccountBudgetPost(models.Model): + _inherit = "account.budget.post" + + # por como esta diseñado ahora se concatena producto con cuenta con un "and" pero según se necesite + # y sea mas facil heredaarlo, podemos hacer que si o si se auno u otro (esa creo que es la mas facil para + # resolver todo con herencia) + product_ids = fields.Many2many('product.product', string='Products') + + def _check_account_ids(self, vals): + if 'product_ids' in vals: + product_ids = self.new({'product_ids': vals['product_ids']}, origin=self).product_ids + else: + product_ids = self.product_ids + if product_ids: + return + else: + # TODO deberiamos ver como mejorar mensaje de super para que diga cuentas o productos + super()._check_account_ids(vals) diff --git a/sale_budget/models/crossovered_budget_line_detail.py b/sale_budget/models/crossovered_budget_line_detail.py new file mode 100644 index 000000000..37dbf0ccb --- /dev/null +++ b/sale_budget/models/crossovered_budget_line_detail.py @@ -0,0 +1,39 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo import api, fields, models + + +class CrossoveredBudgetLineDetail(models.Model): + _name = "crossovered.budget.line.detail" + _description = "crossovered.budget.line.detail" + + crossovered_budget_line_id = fields.Many2one( + "crossovered.budget.lines", "Budget Line", ondelete="cascade", index=True, required=True + ) + product_id = fields.Many2one("product.product", "Product", required=True) + price_unit = fields.Float("Unit Price", required=True, digits="Product Price") + discount = fields.Float( + "Discount (%)", + digits="Discount", + ) + price_subtotal = fields.Float( + compute="_compute_price_subtotal", string="Subtotal", digits="Account" + ) + product_uom_qty = fields.Float("Quantity", digits="Product UoS", required=True) + + @api.onchange("product_id") + def onchange_product_id(self): + for line in self: + # line.price_unit = line.product_id.price + line.price_unit = line.product_id.list_price + + @api.depends("price_unit", "product_uom_qty") + def _compute_price_subtotal(self): + for line in self: + line.price_subtotal = ( + line.product_uom_qty + * line.price_unit + * (1 - (line.discount or 0.0) / 100.0) + ) diff --git a/sale_budget/models/crossovered_budget_lines.py b/sale_budget/models/crossovered_budget_lines.py new file mode 100644 index 000000000..aafe30b9e --- /dev/null +++ b/sale_budget/models/crossovered_budget_lines.py @@ -0,0 +1,135 @@ +from odoo import models, fields, api, _ + + +class CrossoveredBudgetLines(models.Model): + _inherit = "crossovered.budget.lines" + + def _compute_practical_amount(self): + # por ahora es prueba de concepto y hacemos override + # TODO mejorar y hacer herencia (viendo el dif con metodo super) + for line in self: + acc_ids = line.general_budget_id.account_ids.ids + product_ids = line.general_budget_id.product_ids.ids + date_to = line.date_to + date_from = line.date_from + if line.analytic_account_id.id: + analytic_line_obj = self.env['account.analytic.line'] + domain = [('account_id', '=', line.analytic_account_id.id), + ('date', '>=', date_from), + ('date', '<=', date_to), + ] + if acc_ids: + domain += [('general_account_id', 'in', acc_ids)] + if product_ids: + domain += [('product_id', 'in', product_ids)] + + where_query = analytic_line_obj._where_calc(domain) + analytic_line_obj._apply_ir_rules(where_query, 'read') + from_clause, where_clause, where_clause_params = where_query.get_sql() + select = "SELECT SUM(amount) from " + from_clause + " where " + where_clause + + else: + aml_obj = self.env['account.move.line'] + domain = [ + ('date', '>=', date_from), + ('date', '<=', date_to), + ('move_id.state', '=', 'posted') + ] + if acc_ids: + domain += [('general_account_id', 'in', acc_ids)] + if product_ids: + domain += [('product_id', 'in', product_ids)] + where_query = aml_obj._where_calc(domain) + aml_obj._apply_ir_rules(where_query, 'read') + from_clause, where_clause, where_clause_params = where_query.get_sql() + select = "SELECT sum(credit)-sum(debit) from " + from_clause + " where " + where_clause + + self.env.cr.execute(select, where_clause_params) + line.practical_amount = self.env.cr.fetchone()[0] or 0.0 + + def action_open_budget_entries(self): + if self.analytic_account_id: + # if there is an analytic account, then the analytic items are loaded + action = self.env['ir.actions.act_window']._for_xml_id('analytic.account_analytic_line_action_entries') + action['domain'] = [('account_id', '=', self.analytic_account_id.id), + ('date', '>=', self.date_from), + ('date', '<=', self.date_to) + ] + if self.general_budget_id: + domain = [] + acc_ids = line.general_budget_id.account_ids.ids + product_ids = line.general_budget_id.product_ids.ids + if acc_ids: + domain += [('general_account_id', 'in', acc_ids)] + if product_ids: + domain += [('product_id', 'in', product_ids)] + action['domain'] += domain + else: + # otherwise the journal entries booked on the accounts of the budgetary postition are opened + action = self.env['ir.actions.act_window']._for_xml_id('account.action_account_moves_all_a') + domain = [ + ('date', '>=', self.date_from), + ('date', '<=', self.date_to) + ] + acc_ids = line.general_budget_id.account_ids.ids + product_ids = line.general_budget_id.product_ids.ids + if acc_ids: + domain += [('general_account_id', 'in', acc_ids)] + if product_ids: + domain += [('product_id', 'in', product_ids)] + action['domain'] = domain + return action + + planned_detail_ids = fields.One2many( + "crossovered.budget.line.detail", "crossovered_budget_line_id", "Planned Detail" + ) + planned_amount = fields.Monetary( + 'Planned Amount', required=True, + compute='_compute_planned_amount', store=True, readonly=False) + + @api.depends( + "planned_detail_ids.price_subtotal", + ) + def _compute_planned_amount(self): + for line in self.filtered('planned_detail_ids'): + line.planned_amount = sum( + x.price_subtotal for x in line.planned_detail_ids + ) + + # como onchange no va porque el .id no existe todavia, luego lo hacemos bien + # TODO hacerlo de otra manera y no con onchange? computed store? + # @api.onchange('general_budget_id') + def expand_pack_line(self): + if self.general_budget_id.product_ids: + self.planned_detail_ids.unlink() + # TODO agregar contexto de lista de precios + # pricelist=self.order_id.pricelist_id.id + for product in self.general_budget_id.product_ids: + vals = { + "crossovered_budget_line_id": self.id, + "product_id": product.id, + "product_uom_qty": 1.0, + # TODO obtener precio y descuento con nuevo metodo + "price_unit": product.list_price, + # "discount": pack_line.sale_discount, + } + self.planned_detail_ids.create(vals) + + def action_assisted_pack_detail(self): + view = self.env.ref("sale_budget.view_crossovered_budget_lines") + if not self.planned_detail_ids: + self.expand_pack_line() + return { + "name": _("Details"), + "view_type": "form", + "res_model": "crossovered.budget.lines", + "view_id": view.id, + "type": "ir.actions.act_window", + "target": "new", + "readonly": True, + "res_id": self.id, + # "context": dict(self.env.context, pricelist=self.order_id.pricelist_id.id), + } + + def button_save_data(self): + return True diff --git a/sale_budget/models/sale_order.py b/sale_budget/models/sale_order.py new file mode 100644 index 000000000..212c8eb3d --- /dev/null +++ b/sale_budget/models/sale_order.py @@ -0,0 +1,20 @@ +from odoo import fields, models, _ + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def action_create_budget(self): + view = self.env.ref("sale_budget.crossovered_budget_view_tree") + return { + "name": _("Details"), + "view_type": "tree", + # "view_mode": "form", + "res_model": "crossovered.budget", + "view_id": view.id, + "type": "ir.actions.act_window", + "target": "new", + "readonly": True, + # "res_id": self.id, + # "context": dict(self.env.context, pricelist=self.order_id.pricelist_id.id), + } diff --git a/sale_budget/security/ir.model.access.csv b/sale_budget/security/ir.model.access.csv new file mode 100644 index 000000000..7ae60bc9c --- /dev/null +++ b/sale_budget/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_crossovered_budget_line_detail_user,crossovered_budget_line_detail_user,model_crossovered_budget_line_detail,base.group_user,1,1,1,1 +access_crossovered_budget_line_detail_all,crossovered_budget_line_detail_all,model_crossovered_budget_line_detail,,1,0,0,0 diff --git a/sale_budget/views/account_budget_post_views.xml b/sale_budget/views/account_budget_post_views.xml new file mode 100644 index 000000000..ffcc52eb6 --- /dev/null +++ b/sale_budget/views/account_budget_post_views.xml @@ -0,0 +1,17 @@ + + + + + account.budget.post.form + account.budget.post + + + + + + + + + + + diff --git a/sale_budget/views/account_budget_views.xml b/sale_budget/views/account_budget_views.xml new file mode 100644 index 000000000..42d0de250 --- /dev/null +++ b/sale_budget/views/account_budget_views.xml @@ -0,0 +1,34 @@ + + + + + crossovered.budget.view.form + crossovered.budget + + + + +