diff --git a/stay_api/README.rst b/stay_api/README.rst index 95eedb86..61c5e679 100644 --- a/stay_api/README.rst +++ b/stay_api/README.rst @@ -1,10 +1,6 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - -==== -Stay -==== +======== +Stay API +======== .. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -17,7 +13,7 @@ Stay .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fvertical--abbey-lightgray.png?logo=github diff --git a/stay_api/__manifest__.py b/stay_api/__manifest__.py index 510a3982..9cb0dc85 100644 --- a/stay_api/__manifest__.py +++ b/stay_api/__manifest__.py @@ -3,11 +3,11 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - "name": "Stay", - "version": "14.0.3.3.0", + "name": "Stay API", + "version": "14.0.4.0.0", "category": "Lodging", "license": "AGPL-3", - "summary": "API for stay module", + "summary": "REST API for stay module", "author": "Akretion, Odoo Community Association (OCA)", "maintainers": ["alexis-via"], "website": "https://github.com/OCA/vertical-abbey", diff --git a/stay_api/data/res_users.xml b/stay_api/data/res_users.xml index 38b39878..23053e52 100644 --- a/stay_api/data/res_users.xml +++ b/stay_api/data/res_users.xml @@ -6,14 +6,14 @@ --> - + Stay API User stay_api_user - + stay_api_user@example.org + diff --git a/stay_api/migrations/14.0.4.0.0/post-migration.py b/stay_api/migrations/14.0.4.0.0/post-migration.py new file mode 100644 index 00000000..1664fa1d --- /dev/null +++ b/stay_api/migrations/14.0.4.0.0/post-migration.py @@ -0,0 +1,16 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.convert_field_to_html( + env.cr, + "stay_stay", + openupgrade.get_legacy_name("controller_notes"), + "controller_notes", + verbose=False, + ) diff --git a/stay_api/migrations/14.0.4.0.0/pre-migration.py b/stay_api/migrations/14.0.4.0.0/pre-migration.py new file mode 100644 index 00000000..d3895275 --- /dev/null +++ b/stay_api/migrations/14.0.4.0.0/pre-migration.py @@ -0,0 +1,16 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + +_columns_copy = { + "stay_stay": [ + ("controller_notes", None, None), + ], +} + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.copy_columns(env.cr, _columns_copy) diff --git a/stay_api/models/stay_stay.py b/stay_api/models/stay_stay.py index 4f619a48..5c84a473 100644 --- a/stay_api/models/stay_stay.py +++ b/stay_api/models/stay_stay.py @@ -39,7 +39,7 @@ class StayStay(models.Model): controller_phone = fields.Char(tracking=True, string="Phone") controller_mobile = fields.Char(tracking=True, string="Mobile") controller_message = fields.Char(string="Guest Message") - controller_notes = fields.Text(string="Web Form Other Information") + controller_notes = fields.Html(string="Additional Questions") controller_street = fields.Char(string="Address Line 1") controller_street2 = fields.Char(string="Address Line 2") controller_zip = fields.Char(string="ZIP") @@ -204,7 +204,6 @@ def _controller_prepare_create_update(self, cobject, try_match_partner=True): title_code = cobject.title title_id = False if title_code: - # TODO set lang title = self.env["res.partner.title"].search( [("stay_code", "=", title_code)], limit=1 ) @@ -282,7 +281,7 @@ def _controller_prepare_create_update(self, cobject, try_match_partner=True): "controller_zip": cobject.zip, "controller_city": cobject.city, "controller_country_id": country_id, - "controller_notes": "\n".join(notes_list), + "controller_notes": "
".join(notes_list), } if try_match_partner: vals["partner_id"] = self._controller_try_match_partner(vals) diff --git a/stay_api/routers/stay.py b/stay_api/routers/stay.py index 81fb4328..3489572b 100644 --- a/stay_api/routers/stay.py +++ b/stay_api/routers/stay.py @@ -21,7 +21,14 @@ authenticated_partner_env, ) -from ..schemas import StayCreate, StayCreated, StayMatch, StayRead, StayUpdate +from ..schemas import ( + StayCreate, + StayCreated, + StayMatch, + StayRead, + StayUpdate, + StayUpdated, +) logger = logging.getLogger(__name__) @@ -34,7 +41,7 @@ def stay_new( partner: Annotated[Partner, Depends(authenticated_partner)], staycreate: StayCreate, ) -> StayCreated: - logger.debug("Start stay create controller staycreate=%s", staycreate) + logger.info("Stay controller /new called with staycreate=%s", staycreate) sso = env["stay.stay"] company_id = staycreate.company_id if not company_id: @@ -110,12 +117,22 @@ def stay_new( raise HTTPException( status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=error_msg ) + group_id = staycreate.group_id or False + if group_id: + avail_groups = env["stay.group"].search_read([], ["id"]) + avail_group_ids = [group["id"] for group in avail_groups] + if group_id not in avail_group_ids: + error_msg = f"Group ID {group_id} doesn't exist." + logger.error(error_msg) + raise HTTPException( + status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=error_msg + ) vals.update( { "controller_mode": "created", "company_id": company_id, - "group_id": staycreate.group_id or False, + "group_id": group_id, "guest_qty": guest_qty, "arrival_date": arrival_date, "departure_date": departure_date, @@ -131,15 +148,17 @@ def stay_new( logger.info("Mail sent for stay creation notification") except Exception as e: logger.error("Failed to generate stay creation email: %s", e) - return StayCreated( - name=stay.name, - id=stay.id, - company_id=vals["company_id"], - partner_id=vals["partner_id"], - phone=vals["controller_phone"], - mobile=vals["controller_mobile"], - uuid=stay.controller_uuid, - ) + answer_dict = { + "name": stay.name, + "id": stay.id, + "company_id": vals["company_id"], + "partner_id": vals["partner_id"], + "phone": vals["controller_phone"], + "mobile": vals["controller_mobile"], + "uuid": stay.controller_uuid, + } + logger.info("Stay controller /new answer: %s", answer_dict) + return StayCreated(**answer_dict) @stay_api_router.get("/cancel") @@ -148,7 +167,7 @@ def stay_cancel( partner: Annotated[Partner, Depends(authenticated_partner)], staymatch: StayMatch, ): - logger.debug("Start stay cancel controller staymatch=%s", staymatch) + logger.info("Stay controller /cancel called with staymatch=%s", staymatch) stay = env["stay.stay"]._get_stay_from_uuid( staymatch.uuid, "/cancel", ignore_states=("cancel", "done") ) @@ -171,47 +190,47 @@ def stay_read( partner: Annotated[Partner, Depends(authenticated_partner)], staymatch: StayMatch, ) -> StayRead: - logger.debug("Start stay read controller staymatch=%s", staymatch) + logger.info("Stay controller /read called wih staymatch=%s", staymatch) stay = env["stay.stay"]._get_stay_from_uuid( staymatch.uuid, "/read", raise_states=("cancel", "done") ) - if stay: - vals = { - "name": stay.name, - "guest_qty": stay.guest_qty, - "arrival_date": stay.arrival_date, - "departure_date": stay.departure_date, - } - if stay.arrival_time != "unknown": - vals["arrival_time"] = stay.arrival_time - if stay.departure_time != "unknown": - vals["departure_time"] = stay.departure_time - if stay.partner_id: + vals = { + "name": stay.name, + "guest_qty": stay.guest_qty, + "arrival_date": stay.arrival_date, + "departure_date": stay.departure_date, + } + if stay.arrival_time != "unknown": + vals["arrival_time"] = stay.arrival_time + if stay.departure_time != "unknown": + vals["departure_time"] = stay.departure_time + if stay.partner_id: + vals.update( + { + "street": stay.partner_id.street or None, + "street2": stay.partner_id.street2 or None, + "zip": stay.partner_id.zip or None, + "city": stay.partner_id.city or None, + "country_code": stay.partner_id.country_id + and stay.partner_id.country_id.code + or None, + "phone": stay.partner_id.phone or None, + "mobile": stay.partner_id.mobile or None, + "email": stay.partner_id.email or None, + "partner_name": stay.partner_id.name, + } + ) + if hasattr(stay.partner_id, "firstname"): vals.update( { - "street": stay.partner_id.street or None, - "street2": stay.partner_id.street2 or None, - "zip": stay.partner_id.zip or None, - "city": stay.partner_id.city or None, - "country_code": stay.partner_id.country_id - and stay.partner_id.country_id.code - or None, - "phone": stay.partner_id.phone or None, - "mobile": stay.partner_id.mobile or None, - "email": stay.partner_id.email or None, - "partner_name": stay.partner_id.name, + "firstname": stay.partner_id.firstname, + "lastname": stay.partner_id.lastname, } ) - if hasattr(stay.partner_id, "firstname"): - vals.update( - { - "firstname": stay.partner_id.firstname, - "lastname": stay.partner_id.lastname, - } - ) - if stay.partner_id.title and stay.partner_id.title.stay_code: - vals["title"] = stay.partner_id.title.stay_code - return StayRead(**vals) + if stay.partner_id.title and stay.partner_id.title.stay_code: + vals["title"] = stay.partner_id.title.stay_code + logger.info("Stay controller /read answer: %s", vals) + return StayRead(**vals) @stay_api_router.post("/update") @@ -220,30 +239,34 @@ def stay_update( partner: Annotated[Partner, Depends(authenticated_partner)], stayupdate: StayUpdate, ): - logger.debug("Start stay update controller stayupdate=%s", stayupdate) + logger.info("Stay controller /update called wih stayupdate=%s", stayupdate) stay = env["stay.stay"]._get_stay_from_uuid( stayupdate.uuid, "/update", raise_states=("cancel", "done") ) - if stay: - try_match_partner = True - if stay.partner_id: - try_match_partner = False - vals = env["stay.stay"]._controller_prepare_create_update( - stayupdate, try_match_partner=try_match_partner - ) - if not vals: - return False - vals.update( - { - "controller_mode": "updated", - } - ) - logger.debug("Updating stay %s ID %s with vals=%s", stay.name, stay.id, vals) - stay.write(vals) - try: - env.ref("stay_api.stay_controller_notify").sudo().with_context( - action_description=_("updated") - ).send_mail(stay.id) - logger.info("Mail sent for stay update notification") - except Exception as e: - logger.error("Failed to generate stay update email: %s", e) + try_match_partner = True + if stay.partner_id: + try_match_partner = False + vals = env["stay.stay"]._controller_prepare_create_update( + stayupdate, try_match_partner=try_match_partner + ) + if not vals: + return False + vals["controller_mode"] = "updated" + logger.debug("Updating stay %s ID %s with vals=%s", stay.name, stay.id, vals) + stay.write(vals) + try: + env.ref("stay_api.stay_controller_notify").sudo().with_context( + action_description=_("updated") + ).send_mail(stay.id) + logger.info("Mail sent for stay update notification") + except Exception as e: + logger.error("Failed to generate stay update email: %s", e) + answer_dict = { + "name": stay.name, + "id": stay.id, + "phone": vals["controller_phone"], + "mobile": vals["controller_mobile"], + "partner_id": stay.partner_id.id or None, + } + logger.info("Stay controller /update answer: %s", answer_dict) + return StayUpdated(**answer_dict) diff --git a/stay_api/schemas/__init__.py b/stay_api/schemas/__init__.py index 6f8aa721..0dab6dd8 100644 --- a/stay_api/schemas/__init__.py +++ b/stay_api/schemas/__init__.py @@ -3,3 +3,4 @@ from .stay_match import StayMatch from .stay_read import StayRead from .stay_update import StayUpdate +from .stay_updated import StayUpdated diff --git a/stay_api/schemas/stay_update.py b/stay_api/schemas/stay_update.py index 61f7a7cd..a64a5621 100644 --- a/stay_api/schemas/stay_update.py +++ b/stay_api/schemas/stay_update.py @@ -19,7 +19,6 @@ class StayUpdate(BaseModel): departure_time: str departure_note: str = None company_id: int = None - group_id: int = None message: str = None notes_list: list = None country_code: str = None diff --git a/stay_api/schemas/stay_updated.py b/stay_api/schemas/stay_updated.py new file mode 100644 index 00000000..9a8ea393 --- /dev/null +++ b/stay_api/schemas/stay_updated.py @@ -0,0 +1,13 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from pydantic import BaseModel + + +class StayUpdated(BaseModel): + name: str + id: int + phone: str = None + mobile: str = None + partner_id: int = None diff --git a/stay_api/static/description/index.html b/stay_api/static/description/index.html index 4f2f67c0..6119e56a 100644 --- a/stay_api/static/description/index.html +++ b/stay_api/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Stay API -
+
+

Stay API

- - -Odoo Community Association - -
-

Stay

-

Beta License: AGPL-3 OCA/vertical-abbey Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/vertical-abbey Translate me on Weblate Try me on Runboat

This module provides a REST API to create new stays. Useful if you have a web form on your website and you want it to create a new draft stay upon validation.

@@ -392,12 +387,12 @@

Stay

-

Installation

+

Installation

This module depends on the OCA module fastapi from rest-framework.

-

Bug Tracker

+

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 to smash it by providing a detailed and welcomed @@ -405,21 +400,21 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-
diff --git a/stay_api/views/res_partner_title.xml b/stay_api/views/res_partner_title.xml index 1eaa88a5..733e9c8d 100644 --- a/stay_api/views/res_partner_title.xml +++ b/stay_api/views/res_partner_title.xml @@ -12,7 +12,12 @@ - + @@ -22,7 +27,11 @@ - + diff --git a/stay_api/views/stay_stay.xml b/stay_api/views/stay_stay.xml index 0fecd6d6..9f0232f8 100644 --- a/stay_api/views/stay_stay.xml +++ b/stay_api/views/stay_stay.xml @@ -25,8 +25,10 @@ name="controller" string="Web Form" attrs="{'invisible': [('controller_mode', '=', False)]}" + col="1" > + @@ -39,14 +41,24 @@ - - + + + + + + + - + diff --git a/stay_api/wizards/stay_create_partner.py b/stay_api/wizards/stay_create_partner.py index 021f046a..d35d1057 100644 --- a/stay_api/wizards/stay_create_partner.py +++ b/stay_api/wizards/stay_create_partner.py @@ -8,7 +8,7 @@ class StayCreatePartner(models.TransientModel): _name = "stay.create.partner" - _description = "Stay: Create Partner" + _description = "Stay: wizard to create or update partner" stay_id = fields.Many2one("stay.stay", required=True) firstname = fields.Char() @@ -126,7 +126,14 @@ def create_partner(self): partner = self.env["res.partner"].create(vals) partner.message_post(body=_("Partner created from stay web form.")) self.stay_id.write({"partner_id": partner.id}) - self.stay_id.message_post(body=_("Partner created from stay web form.")) + self.stay_id.message_post( + body=_( + "Partner " + "%(partner_name)s created from web form information.", + partner_id=partner.id, + partner_name=partner.display_name, + ) + ) action = { "type": "ir.actions.act_window", "name": _("New Partner"),