diff --git a/.gitignore b/.gitignore index 68bc17f..ec1eee4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,29 @@ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] -*$py.class +/.venv +/.pytest_cache # C extensions *.so # Distribution / packaging .Python +env/ +bin/ build/ develop-eggs/ dist/ -downloads/ +dist_wo_pbr/ eggs/ -.eggs/ -lib/ lib64/ parts/ sdist/ var/ -wheels/ -share/python-wheels/ *.egg-info/ .installed.cfg *.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec +*.eggs # Installer logs pip-log.txt @@ -39,122 +32,50 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ -.nox/ .coverage -.coverage.* +.coverage\.* +.coverage\_* .cache nosetests.xml coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ # Translations *.mo -*.pot -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal +# Pycharm +.idea -# Flask stuff: -instance/ -.webassets-cache +# Eclipse +.settings -# Scrapy stuff: -.scrapy +# Visual Studio cache/options directory +.vs/ +.vscode -# Sphinx documentation -docs/_build/ +# OSX Files +.DS_Store -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +# Django stuff: +*.log -# Spyder project settings -.spyderproject -.spyproject +# Mr Developer +.mr.developer.cfg +.project +.pydevproject -# Rope project settings +# Rope .ropeproject -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ +# Sphinx documentation +docs/_build/ -# pytype static type analyzer -.pytype/ +# Backup files +*~ +*.swp -# Cython debug symbols -cython_debug/ +# OCA rules +!static/lib/ -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +# Auto-generated +ChangeLog +.pre-commit-config-local.yaml diff --git a/event_question_sale/__init__.py b/event_question_sale/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/event_question_sale/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/event_question_sale/__manifest__.py b/event_question_sale/__manifest__.py new file mode 100644 index 0000000..3c27db9 --- /dev/null +++ b/event_question_sale/__manifest__.py @@ -0,0 +1,41 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +{ + "name": "Event question sale", + "version": "1.0.0", + "summary": """ + Set booking order extra products on event question answers. + """, + "description": """ + This module extends the functionnalities of event questions and event tickets sale. + On the event questions you can check the box to be able to add on each answer, + zero, one or more extra products (giving price and qty) that will be added to the sale order. + For the questions asked once per registration, you can choose to add the extra products once or for each attendee. + """, + "author": "RemiFr82", + "contributors": "Hike2River", + "website": "https://remifr82.me", + "license": "LGPL-3", + "category": "Events", + # "price": 0, + # "currency": "EUR", + "application": False, + "installable": True, + "auto_install": False, + "pre_init_hook": "", + "post_init_hook": "", + "uninstall_hook": "", + "excludes": [], + "external_dependencies": [], + "depends": [ + "website_event_questions", + "website_event_sale", + ], + "data": [], + "assets": {}, + "css": [], + "images": [], + "js": [], + "test": [], + "demo": [], + "maintainer": "RemiFr82", +} diff --git a/event_question_sale/models/__init__.py b/event_question_sale/models/__init__.py new file mode 100644 index 0000000..ceda3bf --- /dev/null +++ b/event_question_sale/models/__init__.py @@ -0,0 +1,3 @@ +from . import event_question_answer_product +from . import event_question_answer +from . import event_question diff --git a/event_question_sale/models/event_question.py b/event_question_sale/models/event_question.py new file mode 100644 index 0000000..fbdf8ea --- /dev/null +++ b/event_question_sale/models/event_question.py @@ -0,0 +1,26 @@ +from odoo import api, fields, models, _ + + +class EventQuestion(models.Model): + _inherit = "event.question" + + add_products = fields.Boolean( + string="Add extra products on answers", + help="Checking this box allows", + ) + qty_by_attendees = fields.Boolean( + string="Multiply quantity by attendees count", + help="For the questions asked only once per registration.\n" + "If checked, Odoo will add the extra product for each attendee.\n" + "Otherwise, Odoo will add the extra product only once.", + ) + + @api.onchange("question_type") + def onchange_qtype(self): + if self.question_type != "simple_choice" and self.add_product: + self.add_product = False + + @api.onchange("once_per_order") + def onchange_qtype(self): + if not self.once_per_order and self.qty_by_attendees: + self.qty_by_attendees = False diff --git a/event_question_sale/models/event_question_answer.py b/event_question_sale/models/event_question_answer.py new file mode 100644 index 0000000..2a02c1f --- /dev/null +++ b/event_question_sale/models/event_question_answer.py @@ -0,0 +1,12 @@ +from odoo import api, fields, models, _ + + +class EventQuestionAnswer(models.Model): + _inherit = "event.question.answer" + + extra_product_ids = fields.One2many( + comodel_name="event.question.answer.product", + inverse_name="answer_id", + string="Extra Products", + help="These producs will be added to the sale order of the booking with the given quantity and price.", + ) diff --git a/event_question_sale/models/event_question_answer_product.py b/event_question_sale/models/event_question_answer_product.py new file mode 100644 index 0000000..b620fff --- /dev/null +++ b/event_question_sale/models/event_question_answer_product.py @@ -0,0 +1,66 @@ +from odoo import api, fields, models, _ + + +class EventQuestionAnswerProduct(models.Model): + _name = "event.question.answer.product" + _rec_name = "product_id" + + answer_id = fields.Many2one( + comodel_name="event.question.answer", + string="Answer", + required=True, + ondelete="cascade", + ) + product_id = fields.Many2one( + comodel_name="product.product", + string="Product variant", + ) + price_unit = fields.Float( + string="Unit price", + digits="Product Price", + ) + quantity = fields.Float( + string="Quantity", + digits="Product Unit of Measure", + ) + uom_category_id = fields.Many2one( + related="product_id.uom_id.category_id", + depends=["product_id"], + ) + uom_id = fields.Many2one( + comodel_name="uom.uom", + string="Unit of Measure", + compute="_compute_uom_id", + store=True, + readonly=False, + precompute=True, + ondelete="restrict", + domain="[('category_id', '=', uom_category_id)]", + ) + + def name_get(self): + res = [] + for eqap in self: + res.append( + ( + eqap.id, + _("{} x {} at {}").format( + eqap.quantity, + eqap.product_id.display_name, + eqap.answer_id.question_id.event_id.company_id.currency_id.format( + eqap.price_unit + ), + ), + ) + ) + return res + + @api.depends("product_id") + def _compute_uom_id(self): + for eqap in self: + product = eqap.product_id + uom = eqap.uom_id + if not product: + eqap.uom_id = False + elif not uom or (product.uom_id.id != uom.id): + eqap.uom_id = product.uom_id diff --git a/event_question_sale/models/event_registration_answer.py b/event_question_sale/models/event_registration_answer.py new file mode 100644 index 0000000..58eac00 --- /dev/null +++ b/event_question_sale/models/event_registration_answer.py @@ -0,0 +1,14 @@ +from odoo import api, fields, models, _ + + +class EventRegistrationAnswer(models.Model): + _inherit = "event.registration.answer" + + extra_product_ids = fields.One2many(related="value_answer_id.extra_product_ids") + sale_order_line_ids = fields.Many2many( + comodel_name="sale.order.line", + column1="event_answer_id", + column2="sale_order_line_id", + relation="event_answer_sale_order_line_rel", + string="Related sale order lines", + ) diff --git a/event_question_sale/views/event_event.xml b/event_question_sale/views/event_event.xml new file mode 100644 index 0000000..20f46f2 --- /dev/null +++ b/event_question_sale/views/event_event.xml @@ -0,0 +1,47 @@ + + + + + event.event.view.form.inherit + event.event + + + + o_label_nowrap + + + + + + + 0 + + + + + + + + + + + + + + + + + \ No newline at end of file