i have 2 separate applications msk and mahalla, in mahalla i have questionnaires and in msk i have services and orders, i wanna make questionnaires free and with gift, as gift i wanna give them free services for msk services for given date range and given limits ( like 3 times in given period) when user finishes questionnaire, and i wanna do ti via promocode, what you should do is add this functionality to my app, i will provide my code snippets from both apps, keep in mind that these apps are entirely separate but users(res.partner) have same ids in both platforms, when creating questionnaire we set prize for questionnaire like cleaning service for month, and if user finishes questionnaire we will assign to user promocode, all thing should be automatic, user finishes questionnaire(2 type of questionnaires ordinary and one that have prize) user will have access to the free service for given time, user just go to services and use it freely

MAHALLAM APP FILES:
questionnaire.py:
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from .mixins import LocalizationMixin


class Questionnaire(LocalizationMixin, models.Model):
    _name = 'questionnaire'
    _description = "So'rovnomalar"

    name = fields.Char(string=_('Name'), translate=True)
    is_finished = fields.Boolean(default=False, store=True)
    user_id = fields.Many2one('res.partner', string=_('User'))
    image = fields.Binary(string=_('Image'))
    question_ids = fields.One2many('question', 'questionnaire_id', string=_('Questions'))
    start_date = fields.Date(string=_('Start Date'), default=fields.Date.today)
    end_date = fields.Date(string=_('End Date'), default=fields.Date.today)

    location_type = fields.Selection([
        ('all', _('All')),
        ('region', _('Region')),
        ('district', _('District')),
        ('mahalla', _('Mahalla'))
    ], string=_('Location Type'), required=True, default='all')

    to_all = fields.Boolean(string=_('Is Republic'))
    region_id = fields.Many2many('region', string=_("Region"), required=False)
    to_region = fields.Boolean()
    district_id = fields.Many2many('district', string=_("District"), required=False)
    to_district = fields.Boolean()
    mahalla_id = fields.Many2many('mahalla', string=_("Mahalla"), required=False)
    to_mahalla = fields.Boolean()

    @api.model
    def create(self, vals):
        vals = self._get_user_localization_vals(vals)
        record = super(Questionnaire, self).create(vals)
        return record

    def write(self, vals):
        if self.env.user.type == 'moderator':
            vals = self._get_user_localization_vals(vals)
        return super(Questionnaire, self).write(vals)

    @api.onchange('to_all')
    def _onchange_to_all(self):
        if self.to_all:
            domain = []
            mahallas = self.env['mahalla'].search(domain)
            self.mahalla_id = [(6, 0, mahallas.ids)]
            self.region_id = False
            self.district_id = False
        else:
            self.mahalla_id = False

    @api.onchange('to_region', 'region_id')
    def _onchange_region_selection(self):
        if self.to_region and self.region_id:
            districts = self.env['district'].search([('region_id', 'in', self.region_id.ids)])
            mahallas = self.env['mahalla'].search([('district_id', 'in', districts.ids)])
            self.mahalla_id = [(6, 0, mahallas.ids)]
            self.district_id = False
        elif not self.to_region:
            if not self.to_all and not self.to_district:
                self.mahalla_id = False

    @api.onchange('to_district', 'district_id')
    def _onchange_district_selection(self):
        if self.to_district and self.district_id:
            mahallas = self.env['mahalla'].search([('district_id', 'in', self.district_id.ids)])
            self.mahalla_id = [(6, 0, mahallas.ids)]
        elif not self.to_district:
            if not self.to_all and not self.to_region:
                self.mahalla_id = False

    @api.onchange('location_type')
    def on_change_location_type(self):
        if self.location_type == 'all':
            self.to_all = True
            self.to_region = True
            self.to_district = True
            self.to_mahalla = True
            self._onchange_to_all()
        elif self.location_type == 'region':
            self.to_all = True
            self.to_region = False
            self.to_district = True
            self.to_mahalla = True
            self._onchange_region_selection()
        elif self.location_type == 'district':
            self.to_all = False
            self.to_region = False
            self.to_district = False
            self.to_mahalla = True
            self._onchange_district_selection()
        elif self.location_type == 'mahalla':
            self.to_all = False
            self.to_region = False
            self.to_district = False
            self.to_mahalla = False

    @api.constrains('location_type', 'region_id', 'district_id', 'mahalla_id')
    def _check_location_requirements(self):
        for record in self:
            if record.location_type == 'region' and not record.region_id:
                raise ValidationError("Specify the region for the announcement.")
            elif record.location_type == 'district':
                if not record.region_id:
                    raise ValidationError("Specify the region for the district announcement.")
                if not record.district_id:
                    raise ValidationError("Specify the district for the announcement.")
            elif record.location_type == 'mahalla':
                if not record.region_id:
                    raise ValidationError("Specify the region for the mahalla announcement.")
                if not record.district_id:
                    raise ValidationError("Specify the district for the mahalla announcement.")
                if not record.mahalla_id:
                    raise ValidationError("Specify the mahalla for the announcement.")
    
    @api.constrains('start_date', 'end_date')
    def _check_date_range(self):
        for record in self:
            if record.start_date > record.end_date:
                raise ValidationError("Start date cannot be higher than end date.")

questionnaire.xml:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
    <!-- Tree and Form Views for Questionnaire -->
    <record id="view_questionnaire_tree" model="ir.ui.view">
        <field name="name">questionnaire.tree</field>
        <field name="model">questionnaire</field>
        <field name="arch" type="xml">
            <tree string="So'rovnomalar">
                <field name="name"/>
                <field name="user_id"/>
                <field name="is_finished"/>
            </tree>
        </field>
    </record>

    <record id="view_questionnaire_form" model="ir.ui.view">
        <field name="name">questionnaire.form</field>
        <field name="model">questionnaire</field>
        <field name="arch" type="xml">
            <form string="So'rovnoma">
                <sheet>
                    <group>
                        <field name="name"/>
                        <field name="image" widget="image"/>
                    </group>
                    <group>
                        <field name="start_date"/>
                        <field name="end_date"/>
                    </group>

                    <group>
                        <field name="location_type"/>
                        <field name="region_id" invisible="to_region"/>
                        <field name="to_region" invisible="1"/>
                        <field
                                name="district_id"
                                invisible="to_district"
                                domain="[('region_id', '=', region_id)]"

                        />
                        <field name="to_district" invisible="1"/>
                        <field
                                name="mahalla_id"
                                invisible="to_mahalla"
                                domain="[('district_id', '=', district_id)]"

                        />
                        <field name="to_mahalla" invisible="1"/>
                    </group>
                    <notebook>
                        <page string="Savollar">
                            <!-- One2many Field to Show Questions in Tree View -->
                            <field name="question_ids">
                                <tree string="Savollar">
                                    <field name="name"/>
                                    <field name="type"/>
                                </tree>
                            </field>
                        </page>
                    </notebook>
                </sheet>
            </form>
        </field>
    </record>

    <!-- Form and Tree Views for Question (Inline in Questionnaire) -->
    <record id="view_question_form" model="ir.ui.view">
        <field name="name">question.form</field>
        <field name="model">question</field>
        <field name="arch" type="xml">
            <form string="Savol">
                <sheet>
                    <group>
                        <field name="name"/>
                        <field name="description"/>
                        <field name="type"/>
                    </group>
                    <group string="Tanlovlar">
                        <!-- One2many Field to Show Options in Tree View -->
                        <field name="option_ids">
                            <tree string="Tanlovlar" editable="bottom">
                                <field name="text"/>
                            </tree>
                        </field>
                    </group>
                </sheet>
            </form>
        </field>
    </record>

    <!-- Tree View for Options (Inline in Question) -->
    <record id="view_option_tree" model="ir.ui.view">
        <field name="name">option.tree</field>
        <field name="model">option</field>
        <field name="arch" type="xml">
            <tree string="Tanlovlar">
                <field name="text"/>
            </tree>
        </field>
    </record>

    <!-- Action for Questionnaire -->
    <record id="action_questionnaire" model="ir.actions.act_window">
        <field name="name">So'rovnomalar</field>
        <field name="res_model">questionnaire</field>
        <field name="view_mode">tree,form</field>
        <field name="view_id" ref="view_questionnaire_tree"/>
    </record>

    <!-- Menu Item for Questionnaire -->
    <menuitem id="menu_questionnaire_root" name="So'rovnomalar" sequence="10" web_icon="mahallam,static/src/img/questionnaire.png"/>
    <menuitem id="menu_questionnaire" name="So'rovnomalar" parent="menu_questionnaire_root" action="action_questionnaire" sequence="10"/>
</odoo>

questionnaire router:
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, Form, Request
from fastapi.responses import JSONResponse
from odoo.addons.fastapi_addon.schemas.questionnaire_schema import (
    QuestionnaireListSchema,
    QuestionnaireQuestionsSchema,
    OptionsQuestionSchema,
    QuestionOptionSubmitSchema,
    QuestionnaireDetailResponse,
)
from odoo.addons.fastapi_addon.schemas.base_schema import PagedCollection, Paging
from typing import Optional, Annotated
from odoo.addons.fastapi_addon.utils.media_url import get_media_url
from odoo import _
from ..dependencies import odoo_env, get_partner_with_session
from odoo.api import Environment
from odoo.addons.base.models.res_partner import Partner
import base64
from datetime import datetime as Datetime

router = APIRouter(tags=['Questionnaire'])


@router.get('/questionnaire/list', response_model=PagedCollection[QuestionnaireListSchema])
async def get_questionnaires(
        request: Request,
        env: Annotated[Environment, Depends(odoo_env)],
        partner: Annotated[Partner, Depends(get_partner_with_session)],
):

    main_home = env['user.home.session'].sudo().search([
        ('partner_id', '=', partner.id),
        ('is_main_home', '=', True)
    ], limit=1)

    if not main_home:
        # raise HTTPException(status_code=404, detail="Main home not found")
        main_home = None

    domain = [('mahalla_id', '=', main_home.home_id.mahalla_id.id),
              ('end_date', '>=', Datetime.today().date())]
    questionnaires = env['questionnaire'].sudo().search([]) # TODO add domain

    for questionnaire in questionnaires:
        user_questionnaire = env['user.questionnaire'].sudo().search([
            ('questionnaire_id', '=', questionnaire.id),
            ('user_id', '=', partner.id),
        ], limit=1)

        if not user_questionnaire:
            user_questionnaire = env['user.questionnaire'].sudo().create({
                "questionnaire_id": questionnaire.id,
                "user_id": partner.id
            })

    domain = [('user_id', '=', partner.id),
              ('questionnaire_id.mahalla_id','=', main_home.home_id.mahalla_id.id), 
              ]
    questionnaires = env['user.questionnaire'].sudo().search(domain)
    count = env['user.questionnaire'].sudo().search_count(domain)

    items = [
        QuestionnaireListSchema(
            id=user_questionnaire.questionnaire_id.id,
            name=str(user_questionnaire.questionnaire_id.name),
            progress=user_questionnaire.progress,
            is_finished=user_questionnaire.is_finished,
            solved_questions=env['user.question'].sudo().search_count(
                [('user_questionnaire_id', '=', user_questionnaire.id)]),
            questions_count=env['question'].sudo().search_count(
                [('questionnaire_id', '=', user_questionnaire.questionnaire_id.id)])
        )
        for user_questionnaire in questionnaires
    ]

    return PagedCollection[QuestionnaireListSchema](
        count=count,
        items=items,
        current=str(request.url)
    )


@router.get('/questionnaire/{questionnaire_id}', response_model=QuestionnaireDetailResponse)
async def get_questionnaire_detail(
        questionnaire_id: int,
        request: Request,
        env: Annotated[Environment, Depends(odoo_env)],
        partner: Annotated[Partner, Depends(get_partner_with_session)],
):
    questionnaire = env['questionnaire'].sudo().search([('id', '=', questionnaire_id)], limit=1)

    if not questionnaire:
        raise HTTPException(status_code=404, detail=_('Questionnaire Not Found'))

    user_questionnaire = env['user.questionnaire'].sudo().search([
        ('questionnaire_id', '=', questionnaire.id),
        ('user_id', '=', partner.id)
    ], limit=1)

    if not user_questionnaire:
        user_questionnaire = env['user.questionnaire'].sudo().create({
            "questionnaire_id": questionnaire.id,
            "user_id": partner.id
        })

    # Fetch all questions for the questionnaire
    questions = env['question'].sudo().search([('questionnaire_id', '=', questionnaire.id)])
    total_questions = len(questions)

    # Fetch all options for the questions in bulk
    all_options = env['option'].sudo().search([('question_id', 'in', questions.ids)])

    # Fetch all user question options in bulk
    user_question_options = env['user.question.option'].sudo().search([
        ('user_question_id.user_questionnaire_id', '=', user_questionnaire.id),
    ])

    # Create a dictionary for quick lookup of marked options
    marked_options = {uqo.option_id.id: uqo.id for uqo in user_question_options}

    question_schema_items = []
    answered_questions = 0

    for question in questions:
        options_data = []
        question_options = all_options.filtered(lambda o: o.question_id.id == question.id)
        question_answered = False

        for option in question_options:
            is_checked = option.id in marked_options
            options_data.append(
                OptionsQuestionSchema(
                    id=option.id,
                    text=option.text,
                    is_checked=is_checked
                )
            )
            if is_checked:
                question_answered = True

        if question_answered:
            answered_questions += 1

        question_schema_items.append(
            QuestionnaireQuestionsSchema(
                id=question.id,
                name=question.name,
                description=question.description if question.description else None,
                image=get_media_url(question._name, question.id, 'image') if question.image else None,
                type=question.type,
                questionnaire_id=question.questionnaire_id.id,
                options=options_data
            )
        )

    progress_percentage = (answered_questions / total_questions) * 100 if total_questions > 0 else 0
    is_active = True if questionnaire.end_date > Datetime.today().date() else False

    return QuestionnaireDetailResponse(
        id=questionnaire.id,
        name=questionnaire.name,
        image=get_media_url(questionnaire._name, questionnaire.id, 'image') if questionnaire.image else None,
        questions=question_schema_items,
        total_questions=total_questions,
        answered_questions=answered_questions,
        progress_percentage=round(progress_percentage, 2),
        start_date=questionnaire.start_date,
        end_date=questionnaire.end_date,
        is_active=is_active
    )


@router.post('/questionnaire/{questionnaire_id}/question/answer')
async def question_submit(
        questionnaire_id: int,
        data: QuestionOptionSubmitSchema,
        request: Request,
        env: Annotated[Environment, Depends(odoo_env)],
        partner: Annotated[Partner, Depends(get_partner_with_session)],
):
    questionnaire = env['questionnaire'].sudo().search([('id', '=', questionnaire_id)], limit=1)

    if not questionnaire:
        return HTTPException(status_code=404, detail=_('Questionnaire Not Found'))

    questionnaire_user = env['user.questionnaire'].sudo().search([
        ('questionnaire_id', '=', questionnaire_id),
        ('user_id', '=', partner.id)
    ], limit=1)

    if not questionnaire_user:
        questionnaire_user = env['user.questionnaire'].sudo().create({
            'questionnaire_id': questionnaire_id,
            'user_id': partner.id
        })

    question = env['question'].sudo().search([
        ('id', '=', data.question_id),
        ('questionnaire_id', '=', questionnaire_id)
    ], limit=1)

    if not question:
        raise HTTPException(status_code=400, detail=_('This Question does not belong to this Questionnaire'))

    question_user = env['user.question'].sudo().search([
        ('user_questionnaire_id', '=', questionnaire_user.id),
        ('question_id', '=', data.question_id)
    ], limit=1)

    if not question_user:
        question_user = env['user.question'].sudo().create({
            'user_questionnaire_id': questionnaire_user.id,
            'question_id': question.id,
            'is_answered': True
        })

    # Get all existing option_users for this question_user
    existing_option_users = env['user.question.option'].sudo().search([
        ('user_question_id', '=', question_user.id)
    ])

    # Create a set of option_ids for efficient lookup
    submitted_option_ids = set(data.option_ids)

    for option_user in existing_option_users:
        if option_user.option_id.id not in submitted_option_ids:
            # Delete option_user if it's not in the submitted option_ids
            option_user.sudo().unlink()

    for opt in data.option_ids:
        option = env['option'].sudo().search([('id', '=', opt), ('question_id', '=', question.id)], limit=1)
        if not option:
            raise HTTPException(status_code=400,
                                detail=_(f'The Option with id {opt} does not belong to question with id {question.id}'))

        option_user = env['user.question.option'].sudo().search([
            ('user_question_id', '=', question_user.id),
            ('option_id', '=', option.id)
        ])
        if not option_user:
            option_user = env['user.question.option'].sudo().create({
                'user_question_id': question_user.id,
                'option_id': option.id
            })

    questionnaire_user._compute_progress()
    questionnaire_user._compute_is_finished()

    return JSONResponse(status_code=201, content={"message": "Success"})


@router.post("/questionnaire/{questionnaire_id}/submit")
async def questionnaire_submit(
        questionnaire_id: int,
        env: Annotated[Environment, Depends(odoo_env)],
        partner: Annotated[Partner, Depends(get_partner_with_session)],
):
    # Find the user's questionnaire
    user_questionnaire = env['user.questionnaire'].sudo().search([
        ('questionnaire_id', '=', questionnaire_id),
        ('user_id', '=', partner.id)
    ], limit=1)

    if not user_questionnaire:
        raise HTTPException(status_code=404, detail=_("Questionnaire not found for this user"))

    # Recompute progress and is_finished
    user_questionnaire._compute_progress()
    user_questionnaire._compute_is_finished()

    # Check if all questions are answered
    total_questions = env['question'].sudo().search_count([
        ('questionnaire_id', '=', questionnaire_id)
    ])
    answered_questions = env['user.question'].sudo().search_count([
        ('user_questionnaire_id', '=', user_questionnaire.id),
        ('is_answered', '=', True)
    ])

    if answered_questions < total_questions:
        return JSONResponse(
            status_code=400,
            content={
                "detail": _("Not all questions are answered"),
                "progress": user_questionnaire.progress,
                "total_questions": total_questions,
                "answered_questions": answered_questions
            }
        )

    # If all questions are answered, mark as finished
    user_questionnaire.write({
        'is_finished': True,
        'progress': 100
    })

    return JSONResponse(
        status_code=200,
        content={
            "message": _("Questionnaire submitted successfully"),
            "is_finished": user_questionnaire.is_finished,
            "progress": user_questionnaire.progress
        }
    )


questionnaire schema:
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime as DateTime



class QuestionnaireListSchema(BaseModel):
    id: int
    name: Optional[str] = None
    progress: float
    is_finished: bool
    solved_questions: int
    questions_count: int


class OptionsQuestionSchema(BaseModel):
    id: int
    text: str
    is_checked: bool


class QuestionnaireQuestionsSchema(BaseModel):
    id: int
    name: str
    description: Optional[str]
    image: Optional[str]
    type: str
    questionnaire_id: int
    options: List[OptionsQuestionSchema] = None


class QuestionOptionSubmitSchema(BaseModel):
    question_id: int
    option_ids: List[int]


class QuestionnaireDetailResponse(BaseModel):
    id: int
    name: str
    image: Optional[str]
    questions: List[QuestionnaireQuestionsSchema]
    total_questions: int
    answered_questions: int
    progress_percentage: float
    start_date: Optional[DateTime] = None
    end_date: Optional[DateTime] = None
    is_active: Optional[bool] = False


MSK FILES

msk_service.py:
from datetime import datetime, date
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError


class MskService(models.Model):
    _name = "msk.service"
    _description = _("Msk Service")
    _rec_name = "service_id"

    opening_time = fields.Char(string=_("Start time"), required=True)
    opening_times = fields.Datetime(string=_("Start time"), required=True)
    closing_time = fields.Char(string=_("End time"), required=True)
    is_active = fields.Boolean(string=_("Is Active"), default=True,
                                help=_("If this service is not working, uncheck this field"))
    description = fields.Text(string=_("Description"), required=True)
    discount = fields.Float(string=_("Discount"), defgault=0)
    price_for_call = fields.Float(string=_("Price for call"), required=True)
    discount_start_date = fields.Date(
        string=_("Discount Start Date"),
        help=_("Date when the discount becomes active")
    )
    discount_end_date = fields.Date(
        string=_("Discount End Date"),
        help=_("Date when the discount expires")
    )
    cancellation_time = fields.Integer(string=_("Cancellation Time in Minutes"), required=True)
    msk_id = fields.Many2one("res.users", string=_("MSK"), required=True)
    service_id = fields.Many2one("service", string=_("Service"), required=True)
    mahalla_id = fields.Many2one("mahalla", string=_("Mahalla"))
    employee_ids = fields.Many2many(
        "employee",
        "msk_service_employee_rel",
        "msk_service_id",
        "employee_id",
        string=_("Employees")
    )
    image = fields.Binary(string=_('Image'))

    # Define a unique constraint
    _sql_constraints = [
        (
            "msk_service_unique",
            "unique(msk_id, service_id)",
            "The combination of MSK and Service must be unique.",
        )
    ]

    @api.constrains("msk_id", "service_id")
    def _check_unique_msk_service(self):
        for record in self:
            duplicate = self.search_count(
                [
                    ("msk_id", "=", record.msk_id.id),
                    ("service_id", "=", record.service_id.id),
                    ("id", "!=", record.id),  # Exclude the current record
                ]
            )
            if duplicate:
                raise ValidationError(
                    "The combination of MSK and Service must be unique."
                )

    @api.constrains("opening_time", "closing_time")
    def _check_opening_closing_time(self):
        time_format = "%H:%M"  # 24-hour format: 'HH:MM'
        for record in self:
            try:
                opening_time_obj = datetime.strptime(record.opening_time, time_format)
                closing_time_obj = datetime.strptime(record.closing_time, time_format)

                # Ensure opening time is before closing time
                if opening_time_obj >= closing_time_obj:
                    raise ValidationError("Opening time must be before closing time.")

            except ValueError:
                raise ValidationError("Time must be in the format HH:MM (24-hour).")

            # Ensure times are within the 24-hour range
            if not (record.opening_time >= "00:00" and record.closing_time <= "23:59"):
                raise ValidationError("Time must be within the range 00:00 to 23:59.")

    @api.model
    def create(self, vals):
        if 'msk_id' not in vals:
            vals['msk_id'] = self.env.user.id
        return super(MskService, self).create(vals)
    
    @api.constrains('discount_start_date', 'discount_end_date')
    def _check_discount_dates(self):
        for record in self:
            if record.discount_start_date and record.discount_end_date:
                if record.discount_start_date > record.discount_end_date:
                    raise ValidationError(_("Discount Start Date must be before or equal to Discount End Date."))
            elif record.discount_start_date or record.discount_end_date:
                raise ValidationError(_("Both Discount Start Date and Discount End Date must be set."))
    
    @api.model
    def update_expired_discounts(self):
        """
        This method sets the discount to 0 for all services where the discount_end_date has passed.
        """
        today = date.today()
        expired_services = self.search([
            ('discount_end_date', '<', today),
            ('discount', '>', 0)
        ])
        if expired_services:
            expired_services.write({'discount': 0.0})
            expired_services.write({'discount_start_date': False, 'discount_end_date': False})
order_msk_service.py:
import xmlrpc.client
from odoo.exceptions import UserError
from odoo.addons.fastapi_addon.dependencies import cancel_transaction
from odoo import api, fields, models, _
import os
from odoo.exceptions import ValidationError
REMOTE_ODOO_URL = os.getenv('REMOTE_ODOO_URL', 'https://mahallam-backend.uicgroup.tech/' )
REMOTE_ODOO_DB = os.getenv('REMOTE_ODOO_DB', 'mahallam')
REMOTE_ODOO_USERNAME = os.getenv('REMOTE_ODOO_USERNAME', 'admin')
REMOTE_ODOO_PASSWORD = os.getenv('REMOTE_ODOO_PASSWORD', '7488b7530ebdf32565285214ad246e43dd383cbf')

common = xmlrpc.client.ServerProxy(f'{REMOTE_ODOO_URL}/xmlrpc/2/common')
uid = common.authenticate(REMOTE_ODOO_DB, REMOTE_ODOO_USERNAME, REMOTE_ODOO_PASSWORD, {})
models_xml = xmlrpc.client.ServerProxy(f'{REMOTE_ODOO_URL}/xmlrpc/2/object')


class OrderMskService(models.Model):
    _name = "order.msk.service"
    _description = _("Order MSK Service")
    _rec_name = "order_number"

    msk_service_id = fields.Many2one('msk.service', string=_('MSK Servis'), readonly=True)
    status = fields.Selection(
        [
            ("in_review", _("In Review")),
            ("in_progress", _("In Progress")),
            ("payment_pending", _("Payment Pending")),
            ("finished", _("Finished")),
            ('cancelled', _('Cancelled')),
            ('rejected', _('Rejected')),
        ],
        string=_('Status'),
        group_expand='_expand_status'
    )
    order_number = fields.Char(string=_("Order Number"), readonly=True)

    home_id = fields.Integer(string=_("Home"), readonly=True)
    home_info = fields.Text(string=_('Home Info'), readonly=True)

    user_full_name = fields.Char(string=_('User Full Name'), readonly=True)
    user_phone_number = fields.Char(string=_('User Phone Number'), readonly=True)

    user_id = fields.Integer(string=_("User"), readonly=True)
    start_time = fields.Datetime(string=_("Order Start Time"))
    end_time = fields.Datetime(string=_("Order End Time"))
    payment_method = fields.Selection(
        [("cash", _("Cash")), ("card", _("Credit Card"))], string=_("Payment Method"), readonly=True
    )
    employee_id = fields.Many2one("employee", string=_("Employee Manager"))
    employee_ids = fields.Many2many("employee", string=_("Employees"))
    available_employee_ids = fields.Many2many("employee", string=_('Available Employees'), compute='_available_employees')
    order_cancel_reason_id = fields.Many2one(
        "order.cancel.reason", string=_("Cancel Reason"), readonly=True
    )
    order_cancel_reason_text = fields.Text(
        string=_("Cancel Reason Text"), readonly=True
    )
    reject_reason_text = fields.Text(
        string=_("Reject Reason")
    )
    employee_order_msk_service_ids = fields.One2many(
        "employee.order.msk.service",
        "order_msk_service_id",
        string=_("Employee Order MSK Services"),
    )
    user_comment = fields.Text(string=_("Foydalanuvchi Izohi"), readonly=True)
    previous_employee_id = fields.Integer(string=_('Previous Employee'))
    is_rated = fields.Boolean(string=_("Is Rated"), default=False, readonly=True)

    discount = fields.Float(string=_("Discount"))
    discount_value = fields.Float(string=_("Discount Value"))
    price_for_call = fields.Float(string=_("Price For Call"), readonly=True)
    price_for_service = fields.Float(string=_("Price For Service"))
    total_price = fields.Float(string=_("Full Price"), compute="_compute_total_price", store=True)
    remaining_price = fields.Float(string=_("Remaining Price"))
    discounted_price_for_call = fields.Float(string=_("Discounted Price For Call"), store=True)
    discounted_price_for_service = fields.Float(string=_("Discounted Price For Service"), store=True)
    total_discounted_price = fields.Float(string=_("Discounted Total Price"), store=True)

    @api.onchange('msk_service_id')
    def _onchange_msk_service_id(self):
        if self.msk_service_id:
            self.discount = self.msk_service_id.discount

    @api.depends('start_time', 'end_time')
    def _available_employees(self):
        for record in self:
            available_employee_domain = record._get_employee_domain()

            available_employees = self.env['employee'].search(available_employee_domain)

            self.available_employee_ids =[(6, 0, [employee.id for employee in available_employees])]

    def _get_employee_domain(self):
        if self.start_time and self.end_time:
            # Ensure msk_service_id is valid before creating the domain
            domain = [('id', 'not in', self._get_overlapping_employees())]
            if self.msk_service_id:
                domain.append(('mahalla_service_id', 'in', [self.msk_service_id.id]))
        else:
            # If start_time or end_time are missing, return all employees but ensure msk_service_id is valid
            domain = [('id', 'in', self.env['employee'].search([]).ids)]
            if self.msk_service_id:
                domain.append(('mahalla_service_id', 'in', [self.msk_service_id.id]))

        return domain

    def _get_overlapping_employees(self):
        """
        This helper method checks for employees who have orders that overlap with the current order
        based on its start_time and end_time.
        """
        overlapping_employees = self.env['employee.order.msk.service'].search([
            ('order_msk_service_id.start_time', '<=', self.end_time),
            ('order_msk_service_id.end_time', '>=', self.start_time),
        ])

        # Extract the employee ids that have overlapping orders
        overlapping_employee_ids = overlapping_employees.mapped('employee_id.id')

        return overlapping_employee_ids

    @api.model
    def create(self, vals):
        if 'msk_service_id' in vals:
            msk_service = self.env['msk.service'].browse(vals['msk_service_id'])
            vals['discount'] = msk_service.discount

        res = super().create(vals)

        if "employee_id" in vals and vals["employee_id"]:
            self.env["employee.order.msk.service"].create_from_order(res)
        return res

    def write(self, vals):
        if 'msk_service_id' in vals:
            msk_service = self.env['msk.service'].browse(vals['msk_service_id'])
            vals['discount'] = msk_service.discount
        super(OrderMskService, self).write(vals)
        for record in self:
            if record.status in ["finished", "cancelled", "rejected"]:
                raise UserError(
                    _("You cannot update an order with status: %s") % record.status
                )
        res = super().write(vals)

        # if 'status' in vals and vals['status'] == 'rejected':
        #     # Trigger the action to open the reject reason form
        #     self.action_open_reject_reason_form() # TODO this is not working fix it and force it to work 💪
        for record in self:
            status_name = dict(self._fields['status'].selection).get(record.status)
            if 'status' in vals:
                data = {
                    'id': record.id,
                    'order_number': record.order_number,
                    'status': status_name,
                    'user_id': record.user_id,
                }
                result = models_xml.execute_kw(REMOTE_ODOO_DB, uid, REMOTE_ODOO_PASSWORD,
                                        'msk.order.notification', 'create_order_notification', [data])


        if "employee_ids" in vals:
            for record in self:
                if record.employee_ids:
                    for employee in record.employee_ids:
                        self.env["employee.order.msk.service"].create_from_order(record, employee)

        return res

    @api.onchange('employee_id')
    def _onchange_employee_id(self):
        """Automatically update employee_ids with the selected employee_id."""
        previous_employee_id = self.previous_employee_id

        if self.employee_id:
            if self.employee_id.id not in self.employee_ids.ids:
                self.employee_ids = [(4, self.employee_id.id)]

            if previous_employee_id and previous_employee_id != self.employee_id.id:
                self.employee_ids = [(3, previous_employee_id)]

        elif previous_employee_id:
            self.employee_ids = [(3, previous_employee_id)]

        self.previous_employee_id = self.employee_id.id if self.employee_id else None

    def action_open_reject_reason_form(self):
        print('\n\n\n\n\nhello world\n\n\n\n\n')
        # Opens the rejection wizard form
        action = self.env.ref('msk.action_order_reject_wizard').read()[0]
        print(action)
        return {
            'type': 'ir.actions.act_window',
            'name': 'Reject Order',
            'res_model': 'order.reject.wizard',  # Your wizard model
            'view_mode': 'form',
            'target': 'new',
            'context': {
                'default_order_msk_service_id': self.id,  # Pass the order to the wizard
            },
        }

    @api.model
    def _expand_status(self, statuses, domain, order):
        # Return all possible statuses for grouping
        return [key for key, _ in self._fields['status'].selection]

    def action_reject_order(self, reject_notes):
        if self.status != 'in_progress':
                    raise UserError("Only orders in progress can be rejected.")
        self.write({
            'status': 'rejected',
            'reject_reason_text': reject_notes,
        })

    @api.depends('price_for_call', 'price_for_service', 'discount', 'remaining_price')
    def _compute_total_price(self):
        for record in self:
            total_price = record.price_for_call + record.price_for_service
            record.total_price = total_price

            discount_value = (record.discount / 100.0) * total_price if record.discount else 0.0
            record.discount_value = discount_value

            record.discounted_price_for_call = record.price_for_call - (record.price_for_call * (record.discount / 100.0)) if record.discount else record.price_for_call
            record.discounted_price_for_service = record.price_for_service - (record.price_for_service * (record.discount / 100.0)) if record.discount else record.price_for_service
            record.total_discounted_price = record.discounted_price_for_call + record.discounted_price_for_service

            remaining_price = record.total_discounted_price - record.discounted_price_for_call
            if remaining_price < 0:
                raise ValidationError(_("Remaining price cannot be negative. Please check the input values."))
            record.remaining_price = remaining_price

msk_srevice.xml:
<odoo>
    <!-- Tree View for Msk Service -->
    <record id="view_msk_service_tree" model="ir.ui.view">
        <field name="name">msk.service.tree</field>
        <field name="model">msk.service</field>
        <field name="arch" type="xml">
            <tree string="MSK Service">
                <field name="service_id"/>
                <field name="opening_time"/>
                <field name="closing_time"/>
                <field name="is_active"/>
                <field name="price_for_call"/>
                <field name="discount"/>
                <field name="discount_start_date"/>
                <field name="discount_end_date"/>
            </tree>
        </field>
    </record>

    <!-- Form View for Msk Service -->
    <record id="view_msk_service_form" model="ir.ui.view">
        <field name="name">msk.service.form</field>
        <field name="model">msk.service</field>
        <field name="arch" type="xml">
            <form string="MSK Service">
                <sheet>
                    <group>
                        <group>
                            <field name="msk_id" groups="base.group_system"/>
                            <field name="service_id"/>
                            <field name="opening_time" widget="InputChanger" options="{'mask': 'time', 'inputType': 'number'}"/>
                            <field name="closing_time"  widget="InputChanger" options="{'mask': 'time', 'inputType': 'number'}"/>
                            <field name="cancellation_time"/>
                            <field name="mahalla_id" groups="base.group_system"/>
                            <field name="image" widget="image" required="1"/>
                            <field name="discount"/>
                        </group>
                        <group>
                            <field name="price_for_call"/>
                            <field name="is_active"/>
                            <field name="description"/>
                            <field name="employee_ids" widget="many2many"/>
                            <field name="discount"/>
                            <field name="discount_start_date"/>
                            <field name="discount_end_date"/>
                        </group>
                    </group>
                </sheet>
            </form>
        </field>
    </record>

    <!-- Action to open Msk Service views -->
    <record id="action_msk_service" model="ir.actions.act_window">
        <field name="name">MSK Services</field>
        <field name="res_model">msk.service</field>
        <field name="view_mode">tree,form</field>
    </record>

    <!-- Menu item to access Msk Services -->
    <menuitem id="menu_msk_service_root" name="MSK Services" sequence="10"/>
    <menuitem id="menu_msk_service" name="MSK Services" parent="menu_msk_service_root" action="action_msk_service"
              sequence="-1"/>
</odoo>

order_msk_service.xml:
<odoo>
    <!-- Tree View for Order MSK Service -->
    <record id="view_order_msk_service_tree" model="ir.ui.view">
        <field name="name">order.msk.service.tree</field>
        <field name="model">order.msk.service</field>
        <field name="arch" type="xml">
            <tree string="Order MSK Services">
                <field name="order_number"/>
                <field name="price_for_call" readonly="1"/>
                <field name="price_for_service"/>
                <field name="total_price" readonly="1"/>
                <field name="discount"/>
                <field name="status"/>
                <field name="employee_id"/>
                <field name="start_time"/>
                <field name="end_time"/>
                <field name="is_rated" readonly="1"/>
            </tree>
        </field>
    </record>

    <!-- Form View for Order MSK Service -->
    <record id="view_order_msk_service_form" model="ir.ui.view">
        <field name="name">order.msk.service.form</field>
        <field name="model">order.msk.service</field>
        <field name="arch" type="xml">
            <form string="Order MSK Service">
                <sheet>
                    <group>
                        <field name="msk_service_id"/>
                        <field name="order_number"/>
                        <field name="price_for_call"/>
                        <field name="price_for_service"/>
                        <field name="discounted_price_for_call" readonly="1"/>
                        <field name="discounted_price_for_service" readonly="1"/>
                        <field name="total_price" readonly="1"/>
                        <field name="total_discounted_price" readonly="1"/>
                        <field name="discount" readonly="1"/>
                        <field name="discount_value" readonly="1"/>
                        <field name="remaining_price" readonly="1"/>
                        <field name="status"/>
                        <field name="is_rated" readonly="1"/>
                        <field name="user_full_name"/>
                        <field name="user_phone_number"/>
                        <field name="home_info"/>
                        <field name="employee_id" domain="[('id', 'in', available_employee_ids)]"/>
                        <field name="employee_ids" widget="many2many_tags" domain="[('id', 'in', available_employee_ids)]"/>
                        <field name="available_employee_ids" invisible="1"/>
                        <field name="start_time"/>
                        <field name="end_time"/>
                        <field name="user_comment"/>
                    </group>
                    <group>
                        <field name="order_cancel_reason_id" widget="one2many_list" invisible="order_cancel_reason_id==False"/>
                        <field name="order_cancel_reason_text" invisible="order_cancel_reason_text==False"/>
                    </group>
                </sheet>
            </form>
        </field>
    </record>

    <!-- Kanban View for Order MSK Service -->
<!-- Kanban View for Order MSK Service -->
<!-- Kanban View for Order MSK Service -->
<record id="view_order_msk_service_kanban" model="ir.ui.view">
    <field name="name">order.msk.service.kanban</field>
    <field name="model">order.msk.service</field>
    <field name="arch" type="xml">
        <kanban default_group_by="status" class="kanban_msk" string="Order MSK Services">
            <field name="status"/>
            <field name="order_number"/>
            <field name="price_for_call" readonly="1"/>
            <field name="price_for_service"/>
            <field name="discounted_price_for_call" readonly="1"/>
            <field name="discounted_price_for_service" readonly="1"/>
            <field name="total_price" readonly="1"/>
            <field name="total_discounted_price" readonly="1"/>
            <field name="discount" readonly="1"/>
            <field name="discount_value" readonly="1"/>
            <field name="remaining_price"/>
            <field name="is_rated" readonly="1"/>
            <field name="start_time"/>
            <field name="employee_id"/>
            <field name="end_time"/>

            <templates>
                <t t-name="kanban-box">
                    <div class="oe_kanban_global_click"
                         t-att-data-id="record.id">
                        <div>
                            <strong>Buyurtma Raqami: <field name="order_number"/></strong>
                        </div>
                        <div>Status: <field name="status"/></div>
                        <div>
                            <field name="price_for_call"/> - <field name="price_for_service"/>
                        </div>
                        <div><field name="start_time"/></div>
                        <div><field name="end_time"/></div>
                    </div>
                </t>
            </templates>
        </kanban>
    </field>
</record>




    <!--  Pivot View for Order MSK Service  -->
    <record id="view_order_msk_service_pivot" model="ir.ui.view">
        <field name="name">order.msk.service.pivot</field>
        <field name="model">order.msk.service</field>
        <field name="arch" type="xml">
            <pivot string="Order Pivot View">
                <field name="price_for_call" readonly="1"/>
                <field name="price_for_service"/>
                <field name="discounted_price_for_call" readonly="1"/>
                <field name="discounted_price_for_service" readonly="1"/>
                <field name="total_price" readonly="1"/>
                <field name="total_discounted_price" readonly="1"/>
                <field name="discount" readonly="1"/>
                <field name="discount_value" readonly="1"/>
                <field name="remaining_price" readonly="1"/>
                <field name="is_rated" readonly="1"/>
                <field name="status" type="row"/>
                <field name="home_id" type="col"/>
            </pivot>
        </field>
    </record>

    <!-- Action to open Order MSK Service views -->
    <record id="action_order_msk_service" model="ir.actions.act_window">
        <field name="name">Order MSK Services</field>
        <field name="res_model">order.msk.service</field>
        <field name="view_mode">kanban,tree,form,pivot</field>

    </record>

    <!-- Menu item to access Order MSK Services -->
    <menuitem id="menu_order_msk_service_root" name="Orders"
              action="action_order_msk_service" sequence="15" web_icon="msk,static/src/images/checkout.png"/>
    <menuitem id="menu_order_msk_service" name="Order List" parent="menu_order_msk_service_root"
              action="action_order_msk_service" sequence="15"/>
</odoo>
service_router:
from datetime import datetime, timedelta
from typing import Annotated, List, Optional
from fastapi import APIRouter, Depends, Request, HTTPException
from odoo.api import Environment
from odoo.addons.base.models.res_partner import Partner
from odoo import _

from odoo.addons.fastapi_addon.schemas.base_schema import PagedCollection, Paging
from odoo.addons.fastapi_addon.schemas.service_schema import ServiceList, ServiceDetail, ServiceComment, CommentRequest, ServiceCommentDetail
from odoo.addons.fastapi_addon.utils.media_url import get_media_url

from ..dependencies import odoo_env, paging, get_partner, get_user_data
import base64
import pytz

router = APIRouter(tags=["Service"])
tashkent_tz = pytz.timezone("Asia/Tashkent")


@router.get("/service/list", response_model=PagedCollection[ServiceList])
async def get_services(
        env: Annotated[Environment, Depends(odoo_env)],
        paging: Annotated[Paging, Depends(paging)],
        request: Request,
):
    """
    Get a list of services with category and description.
    """

    # Fetch the total count of active services
    count = env["msk.service"].sudo().search_count([])

    # Fetch services based on pagination
    services = env["msk.service"].sudo().search([], limit=paging.limit, offset=paging.offset)
    # Prepare the list of services to return
    items = [
        ServiceList(
            id=service.id,
            service_name=service.service_id.name if service.service_id else None,
            opening_time=service.opening_time,
            closing_time=service.closing_time,
            is_active=service.is_active,
            discount=service.discount if service.discount else None,
            icon=get_media_url(service.service_id._name, service.service_id.id, 'icon') if service.service_id and service.service_id.icon else None,
        )
        for service in services
    ]

    # Return the paginated response
    return PagedCollection[ServiceList](
        count=count,
        current=str(request.url).replace("http://", "https://"),
        items=items,
    )


@router.get("/service/{service_id}", response_model=ServiceDetail)
async def get_service_detail(
        service_id: int,
        env: Annotated[Environment, Depends(odoo_env)]
):
    """
    Get detailed information about a service by its ID.
    """
    # Search for the service record by ID
    service = env["msk.service"].sudo().search([("id", "=", service_id)], limit=1)

    # If service is not found, raise a 404 HTTP exception
    if not service:
        raise HTTPException(status_code=404, detail=_("Service not found"))

    # Prepare the service detail data
    return ServiceDetail(
        id=service.id,
        name=service.service_id.name if service.service_id else None,
        opening_time=service.opening_time,
        closing_time=service.closing_time,
        price_for_call=service.price_for_call,
        is_active=service.is_active,
        discount=service.discount if service.discount else None,
        discount_start_date=service.discount_start_date if service.discount_start_date else None,
        discount_end_date=service.discount_end_date if service.discount_end_date else None,
        cancellation_time=service.cancellation_time * 60,
        description=service.description if service.description else None,
        icon=get_media_url(service.service_id._name, service.service_id.id, 'icon') if service.service_id and service.service_id.icon else None,
        image=get_media_url(service._name, service.id, 'image') if service.image else None
    )


@router.get("/service/{service_id}/comments", response_model=PagedCollection[ServiceComment])
async def get_service_comments(
        service_id: int,
        env: Annotated[Environment, Depends(odoo_env)],
        request: Request,
        remote_partner: Annotated[Partner, Depends(get_partner)],
):
    """
    Get comments by service id
    """
    service = env['msk.service'].sudo().search([('id', '=', service_id)], limit=1)
    domain = [("mahalla_service_id", "=", service_id), ('is_approved', '=', 1)]

    if not service:
        raise HTTPException(status_code=404, detail=_("Service not found"))

    comments = env["comment"].sudo().search(domain)
    count = env["comment"].sudo().search_count(domain)

    items = []

    for comment in comments:
        data = get_user_data(comment.user_id)
        image = user_name =None
        if data and data.get('success'):
            image = data.get('image_url')
            user_name = data.get('user_name')

        items.append(ServiceComment(
            id=comment.id,
            user_id=comment.user_id if comment.user_id else None,
            name=user_name,
            content=comment.content,
            rating=comment.rating if comment.rating else None,
            view_content=comment.view_content,
            mahalla_service_id=comment.mahalla_service_id.id if comment.mahalla_service_id else None,
            user_avatar=image,
            is_author=comment.user_id == remote_partner.get('id') if comment.user_id else False,
            create_date=comment.create_date.astimezone(tashkent_tz),
        ))
    return PagedCollection[ServiceComment](
        count=count,
        current=str(request.url),
        items=items
    )


@router.post("/comments/create", response_model=ServiceCommentDetail)
async def comments_create_for_service(
        data: CommentRequest,
        env: Annotated[Environment, Depends(odoo_env)],
        remote_partner: Annotated[Partner, Depends(get_partner)],
):
    if not (1 <= data.rating <= 5):
        raise HTTPException(status_code=400, detail=_("Rating must be between 1 and 5"))

    comment = env['comment'].sudo().create({
        'mahalla_service_id': data.mahalla_service_id,
        'user_id': remote_partner.get('id'),
        'rating': data.rating,
        'is_approved': False,
        'view_content': 0,
        'content': data.content,
    })

    return ServiceCommentDetail(
        id=comment.id,
        user_id=comment.user_id if comment.user_id else None,
        content=comment.content,
        rating=comment.rating if comment.rating else None,
        view_content=comment.view_content,
        mahalla_service_id=comment.mahalla_service_id.id if comment.mahalla_service_id else None,
    )
order_router:
from typing import List, Optional, Annotated
from datetime import datetime, timezone
from fastapi.responses import JSONResponse
from fastapi import APIRouter, Depends, HTTPException, Request, Query
from odoo.api import Environment
from odoo.addons.fastapi_addon.schemas.base_schema import PagedCollection, Paging
from odoo.addons.fastapi_addon.schemas.order_msk_service_schema import (
    OrderMSKServiceSchema,
    OrderMSKServiceDetailSchema,
    PaylovOrderRequest,
    CancelReasonSchema,
    CancelReasonResponse,
    OrderEmployeeSchema
)
from odoo import fields
from odoo.addons.fastapi_addon.utils.media_url import get_media_url
from odoo.addons.base.models.res_partner import Partner
from odoo import _

from ..dependencies import odoo_env, paging, get_partner, validate_user_card, pay_for_transaction, cancel_transaction, get_home_info
from pydantic import BaseModel
from odoo.addons.fastapi_addon.utils.payment_related import create_transaction
from ..utils import calculate_remaining_cancellation_time
import logging
import pytz

router = APIRouter(tags=["Orders"])
tashkent_tz = pytz.timezone("Asia/Tashkent")
logger = logging.getLogger(__name__)

@router.get('/order/cancel_reasons', response_model=List[CancelReasonResponse])
async def get_cancel_reasons(env: Environment = Depends(odoo_env)):
    # Fetch all cancel reasons from the Odoo model
    cancel_reasons = env['order.cancel.reason'].sudo().search([])

    # Prepare the data for the response
    reason_list = [
        {
            "id": reason.id,
            "content": reason.content,
        }
        for reason in cancel_reasons
    ]

    # Return the list of reasons as JSON
    return JSONResponse(content=reason_list)




@router.get("/orders", response_model=PagedCollection[OrderMSKServiceSchema])
async def get_orders_by_user(
        partner: Annotated[Partner, Depends(get_partner)],
        status: Optional[str] = Query(None),  # Accept status filter as a query parameter
        env: Environment = Depends(odoo_env),
        paging: Paging = Depends(paging),
        request: Request = None,
):
    """
    Get a list of orders related to a specific user, with optional filtering by statuses.
    """
    # Search domain for the orders related to the user
    domain = [("user_id", "=", partner.get('id'))]
    order_obj = env["order.msk.service"].sudo()
    orders_all_count = order_obj.search_count(domain)

    # Add status filtering if provided
    if status:
        domain.append(("status", "=", status))

    # Get the count of matching orders
    orders_count = order_obj.search_count(domain)


    # Search for the orders with the applied domain
    orders = order_obj.search(domain, limit=paging.limit, offset=paging.offset)

    # Prepare the data for the response
    order_data = [
        OrderMSKServiceSchema(
            id=order.id if order.id else None,
            service_name=order.msk_service_id.service_id.name if order.msk_service_id else None,
            status=order.status if order.status else None,
            order_number=order.order_number if order.order_number else None,
            user_comment=order.user_comment or None,
            start_time=order.start_time.astimezone(tashkent_tz) if order.start_time else None,
            end_time=order.end_time.astimezone(tashkent_tz) if order.end_time else None,
        )
        for order in orders
    ]

    return PagedCollection[OrderMSKServiceSchema](
        count=orders_count,
        current=str(request.url).replace("http://", "https://"),
        items=order_data,
        all_count=orders_all_count
    )


@router.get("/order/{order_id}", response_model=OrderMSKServiceDetailSchema)
async def get_order_detail(
    partner: Annotated[Partner, Depends(get_partner)],
    order_id: int,
    env: Environment = Depends(odoo_env),
):
    """
    Get detailed information about a specific order by its ID, including dynamic cancellation time.
    """

    order_obj = env["order.msk.service"].sudo()
    order = order_obj.search(
        [("id", "=", order_id), ('user_id', '=', partner.get('id'))],
        limit=1
    )

    if not order:
        logger.error(_(f"Order with ID {order_id} not found for user ID {partner.get('id')}."))
        raise HTTPException(status_code=404, detail=_("Order not found."))

    if not order.msk_service_id:
        logger.error(f"Order ID {order_id} does not have an associated msk_service_id.")
        raise HTTPException(status_code=400, detail=_("Associated service not found."))

    cancellation_time_minutes = order.msk_service_id.cancellation_time or 0

    remaining_cancellation_time = calculate_remaining_cancellation_time(
        creation_time_str=order.create_date,
        cancellation_time_minutes=cancellation_time_minutes
    )

    price_for_call_status = None
    price_for_service_status = None

    transaction_obj = env["transaction"].sudo()
    transactions = transaction_obj.search(
        [("order_msk_id", "=", order_id)],
    )
    if transactions:
        for transaction in transactions:
            if transaction.payment_service_type == "call":
                price_for_call_status = transaction.payment_status
            elif transaction.payment_service_type == "service":
                price_for_service_status = transaction.payment_status
            else:
                price_for_call_status = None
                price_for_service_status = None
    else:
        price_for_call_status = None
        price_for_service_status = None

    order_data = OrderMSKServiceDetailSchema(
        id=order.id,
        name=order.msk_service_id.service_id.name if order.msk_service_id.service_id else None,
        service_name=order.msk_service_id.service_id.name if order.msk_service_id.service_id else None,
        discount_value=order.discount_value if order.discount_value and order.status not in ["in_review", 'in_progress', 'pending', 'cancelled', 'rejected'] else None,
        discount=order.discount if order.discount > 0 and order.status not in ["in_review", 'in_progress', 'pending', 'cancelled', 'rejected'] else None,
        total_price=order.total_price if order.total_price > 0 and order.status not in ["in_review", 'in_progress', 'pending', 'cancelled', 'rejected'] else None,
        total_discounted_price=order.total_discounted_price if order.total_discounted_price > 0 and order.status not in ["in_review", 'in_progress', 'pending', 'cancelled', 'rejected'] else None,
        remaining_price=order.remaining_price if order.remaining_price > 0 and order.status not in ["in_review", 'in_progress', 'pending', 'cancelled', 'rejected'] else None,
        price_for_call=order.price_for_call if order.price_for_call > 0 and order.status not in ['cancelled', 'rejected'] else None,
        discounted_price_for_call=order.discounted_price_for_call if order.discounted_price_for_call > 0 and order.status not in ['cancelled', 'rejected'] else None,
        price_for_service=order.price_for_service if order.price_for_service > 0 and order.status in ['payment_pending', 'finished'] else None,
        discounted_price_for_service=order.discounted_price_for_service if order.discounted_price_for_service > 0 and order.status in ['payment_pending', 'finished'] else None,
        price_for_call_status=price_for_call_status if order.price_for_call > 0 and price_for_call_status and order.status not in ['cancelled', 'rejected'] else None,
        price_for_service_status=price_for_service_status if order.price_for_service > 0 and order.status in ['payment_pending', 'finished'] and price_for_service_status else None,
        status=order.status or None,
        order_number=order.order_number if order.order_number and order.status not in ['pending', 'cancelled', 'rejected'] else None,
        home_id=order.home_id if order.home_id else None,
        home_info=order.home_info or None,
        user_id=order.user_id if order.user_id else None,
        start_time=order.start_time.astimezone(tashkent_tz) if order.start_time else None,
        end_time=order.end_time.astimezone(tashkent_tz) if order.end_time else None,
        cancellation_time=remaining_cancellation_time if order.status == 'in_review' else None,
        payment_method=order.payment_method if order.payment_method and order.status not in ['cancelled', 'rejected'] else None,
        employee_id=order.employee_id.id if order.employee_id and order.status not in ['pending', 'cancelled', 'rejected'] else None,
        employee_name=order.employee_id.full_name if order.employee_id and order.status not in ['pending', 'cancelled', 'rejected'] else "N/A",
        order_cancel_reason_id=order.order_cancel_reason_id.id if order.order_cancel_reason_id and order.status == 'cancelled' else None,
        order_cancel_reason_text=order.order_cancel_reason_text if order.order_cancel_reason_id and order.status == 'cancelled' else None,
        reject_reason_text=str(order.reject_reason_text) if order.status == 'rejected' and order.reject_reason_text else None,
        is_rated=order.is_rated if order.is_rated else False
    )

    logger.error(f"Order ID: {order.id}")
    logger.error(f"Cancellation Time (minutes): {cancellation_time_minutes}")
    logger.error(f"Remaining Cancellation Time (seconds): {remaining_cancellation_time}")

    return order_data







@router.get("/order/{order_id}/employee", response_model=List[OrderEmployeeSchema])
async def get_order_employees(
        partner: Annotated[Partner, Depends(get_partner)],
        order_id: int,
        env: Annotated[Environment, Depends(odoo_env)]
) -> List[OrderEmployeeSchema]:
    employees = env["employee"].sudo().search([
        ("order_ids", "in", [order_id]),
    ])

    if not employees:
        raise HTTPException(
            status_code=404,
            detail=_("Employee associated with the order not found or access denied.")
        )

    employee_data = [
        OrderEmployeeSchema(
            id=employee.id,
            name=employee.full_name or None,
            image=get_media_url(employee._name, employee.id, 'avatar') if employee.avatar else None,
            gender=employee.gender if employee.gender else None
        )
        for employee in employees
    ]
    return employee_data







class CreateOrderRequest(BaseModel):
    start_time: datetime
    msk_service: int
    user_comment: Optional[str]
    payment_method: Optional[str]


# Response model for order creation
class OrderMSKServiceResponse(BaseModel):
    id: int
    order_number: str
    service_name: Optional[str]
    start_time: Optional[datetime]
    create_date: datetime
    home_id: Optional[int]
    home_info: Optional[str]
    user_comment: Optional[str]
    price_for_call: Optional[float]
    price_for_service: Optional[float]
    status: str
    employee_id: Optional[int]
    employee_name: Optional[str]


@router.post("/order/create", response_model=OrderMSKServiceDetailSchema)
async def create_order(
        partner: Annotated[Partner, Depends(get_partner)],
        request_data: CreateOrderRequest,
        env: Environment = Depends(odoo_env)
):
    """
    Create an order for MSK service and return the order details.
    """
    order_model = env["order.msk.service"].sudo()

    service_obj = env["msk.service"].sudo()
    service = service_obj.browse(request_data.msk_service)

    if not service.exists():
        raise HTTPException(status_code=404, detail=_("Service not found"))

    valid_payment_methods = {"cash": "Cash", "card": "Credit Card"}

    # Ensure the provided payment_method is valid
    payment_method = request_data.payment_method
    if payment_method not in valid_payment_methods:
        raise HTTPException(status_code=400, detail=_("Invalid payment method"))

    # Generate a unique order number
    order_number = f"ORD-{service.id}-{datetime.now().strftime('%Y%m%d%H%M%S')}"
    start_time = request_data.start_time.replace(tzinfo=None) if request_data.start_time else None
    # Create the order
    data = get_home_info(partner.get('id'))
    if data.get('success'):
        home_info = data.get('home_info')
        home_id = data.get('home_id')
        user_full_name = data.get('user_name')
        user_phone_number = data.get('phone_number')
    else:
        home_info = home_id = user_full_name = user_phone_number = None
    order_vals = {
        "start_time": start_time,
        "msk_service_id": request_data.msk_service,
        "user_comment": request_data.user_comment or "",
        "price_for_call": service.price_for_call,
        "home_info": home_info,
        "status": "in_review",  # Start with 'new' status
        "order_number": order_number,  # Assign generated order number
        "payment_method": payment_method,
        "user_full_name": user_full_name,
        "user_phone_number": user_phone_number,
        "home_id": home_id,
        "user_id": partner.get('id'),
    }
    new_order = order_model.create(order_vals)
    order_number = f"ORD-{new_order.id:06}"
    new_order.write({"order_number": order_number})

    transaction_values = {
        'user_id': partner.get('id'),
        # 'user_card_id': partner.get(''),
        'amount': service.price_for_call,
        'type': payment_method,
        'order_id': new_order.id,
        'payment_service_type': 'call',
        'payment_status': 'pending'
    }
    create_transaction(env=env, transaction_values=transaction_values)

    # Return the newly created order details
    return OrderMSKServiceDetailSchema(
        id=new_order.id if new_order.id else None,
        name=new_order.msk_service_id.service_id.name if new_order.msk_service_id else None,
        service_name=new_order.msk_service_id.service_id.name if new_order.msk_service_id else None,
        price_for_call=new_order.price_for_call if new_order.price_for_call > 0 else None,
        price_for_service=new_order.price_for_service if new_order.price_for_service > 0 else None,
        status=new_order.status if new_order.status else None,
        order_number=new_order.order_number if new_order.order_number else None,
        home_id=new_order.home_id if new_order.home_id else None,
        home_info=new_order.home_info if new_order.home_info else None,
        user_id=new_order.user_id if new_order.user_id else None,
        start_time=new_order.start_time.astimezone(tashkent_tz) if new_order.start_time else None,
        end_time=new_order.end_time.astimezone(tashkent_tz) if new_order.end_time else None,
        payment_method=new_order.payment_method if new_order.payment_method else None,
        employee_id=new_order.employee_id.id if new_order.employee_id else None,
        employee_name=new_order.employee_id.name if new_order.employee_id else "N/A",
    )




@router.post('/order/{order_id}/pay')
async def pay_for_order(
        partner: Annotated[Partner, Depends(get_partner)],
        order_id: int,
        request_data: PaylovOrderRequest,
        env: Environment = Depends(odoo_env)
):
    order = env['order.msk.service'].sudo().search([
        ('id', '=', order_id),
        ('user_id', '=', partner.get('id'))
    ])
    if not order:
        raise HTTPException(status_code=404, detail=_('Order Not Found'))

    # Validate the user's card
    card_validated = validate_user_card({'user_id': partner.get('id'), 'card_id': request_data.card_id})
    if not card_validated:
        raise HTTPException(status_code=404, detail=_('Card Not Found'))

    # Find all pending transactions for this order
    transactions = env['transaction'].sudo().search([
        ('order_msk_id', '=', order.id),
        ('payment_status', '=', 'pending')
    ])

    if not transactions:
        raise HTTPException(status_code=404, detail=_('There are no unpaid transactions for this order'))

    successful_payments = []
    failed_payments = []

    # Process each transaction
    for transaction in transactions:
        data = {
            'transaction_id': transaction.remote_id,
            'remote_id': transaction.paylov_remote_id,
            'card_id': card_validated.get('card_id'),
            'user_id': partner.get('id')
        }
        response = pay_for_transaction(data)

        # Check if the payment was successful
        if response and response.get('code') == 'success':
            transaction_data = {
                'paylov_remote_id': response.get('remote_id'),
                'payment_status': 'paid',
                'transaction_response': response.get('response_text')
            }
            transaction.sudo().write(transaction_data)
            successful_payments.append({
                'transaction_id': transaction.paylov_remote_id,
                'amount': transaction.amount,
            })
        else:
            # Capture failed payment details
            failed_payments.append({
                'transaction_id': transaction.remote_id,
                'error': response.get('detail', 'Unknown error') if response else 'Unknown error'
            })

    # Prepare response with details of successful and failed payments
    return JSONResponse(status_code=200, content={
        'message': 'Payment Processed',
        'order_id': order.id,
        'successful_payments': successful_payments,
        'failed_payments': failed_payments
    })



@router.post('/order/{order_id}/cancel')
async def cancel_order(
        partner: Annotated[Partner, Depends(get_partner)],
        request_data: CancelReasonSchema,
        order_id: int,
        env: Environment = Depends(odoo_env)
):
    order = env['order.msk.service'].sudo().search([
        ('id', '=', order_id),
        ('user_id', '=', partner.get('id'))
    ])
    if not order:
        raise HTTPException(status_code=404, detail=_('Order Not Found'))

    if order.status == 'cancelled':
        raise HTTPException(status_code=400, detail=_('This Order has already cancelled'))

    if request_data.cancel_reason_id:
        cancel_reason = env['order.cancel.reason'].sudo().browse(request_data.cancel_reason_id)
        if not cancel_reason.exists():
            raise HTTPException(status_code=400, detail=_('Invalid cancel_reason_id provided.'))

    transactions = env['transaction'].sudo().search([('order_msk_id', '=', order_id)])

    if transactions:

        paid_transactions = env['transaction'].sudo().search([
            ('payment_status', '=', 'paid'),
            ('type', '=', 'card'),
            ('order_msk_id', '=', order.id)
        ])
        for paid_transaction in paid_transactions:
            result = cancel_transaction(data={
                'transaction_id': paid_transaction.remote_id,
                'remote_id': paid_transaction.paylov_remote_id,
            })
            if result.get('code') == 'error':
                raise HTTPException(
                    status_code=result.get('status_code') or 400,
                    detail=result.get('detail')
                )
        transactions.write({'payment_status': 'cancelled'})

    order.write({
        'order_cancel_reason_id': request_data.cancel_reason_id,
        'order_cancel_reason_text': request_data.cancel_reason_text,
        'status': 'cancelled'
    })

    return JSONResponse(
        content={
            "status":_( "success"),
            "message": _(f"Order {order_id} has been successfully cancelled."),
            "cancel_reason_id": request_data.cancel_reason_id,
            "cancel_reason_text": request_data.cancel_reason_text
        },
        status_code=200
    )
order_msk_service schema:
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, model_validator


class OrderMSKServiceSchema(BaseModel):
    id: Optional[int] = None
    service_name: Optional[str] = None
    status: Optional[str] = None
    order_number: Optional[str] = None
    user_comment: Optional[str] = None
    start_time: Optional[datetime] = None
    end_time: Optional[datetime] = None


class OrderMSKServiceDetailSchema(BaseModel):
    id: Optional[int] = None
    name: Optional[str] = None
    service_name: Optional[str] = None
    total_price: Optional[float] = None
    total_discounted_price: Optional[float] = None
    remaining_price: Optional[float] = None
    discount: Optional[float] = None
    discount_value: Optional[float] = None
    price_for_call: Optional[float] = None
    discounted_price_for_call: Optional[float] = None
    price_for_call_status: Optional[str] = None
    price_for_service: Optional[float] = None
    discounted_price_for_service: Optional[float] = None
    price_for_service_status: Optional[str] = None
    status: Optional[str] = None
    order_number: Optional[str] = None
    home_id: Optional[int] = None
    home_info: Optional[str] = None
    user_id: Optional[int] = None
    start_time: Optional[datetime] = None
    end_time: Optional[datetime] = None
    cancellation_time: Optional[int] = None
    payment_method: Optional[str] = None
    employee_id: Optional[int] = None
    employee_name: Optional[str] = None
    order_cancel_reason_id: Optional[int] = None
    order_cancel_reason_text: Optional[str] = None
    reject_reason_text: Optional[str] = None
    is_rated: Optional[bool] = False


class OrderEmployeeSchema(BaseModel):
    id: Optional[int] = None
    name: Optional[str] = None
    image: Optional[str] = None
    gender: Optional[str] = None


class PaylovOrderRequest(BaseModel):
    card_id: str


class CancelReasonSchema(BaseModel):
    cancel_reason_id: Optional[int] = None
    cancel_reason_text: Optional[str] = None

    @model_validator(mode='before')
    def check_at_least_one(cls, values):
        cancel_reason_id = values.get('cancel_reason_id')
        cancel_reason_text = values.get('cancel_reason_text')
        if not cancel_reason_id and not cancel_reason_text:
            raise ValueError('Either cancel_reason_id or cancel_reason_text must be provided.')
        return values


class CancelReasonResponse(BaseModel):
    id: int
    content: str
service_schema:
from datetime import timedelta, datetime, date
from pydantic import BaseModel
from typing import Optional


class ServiceList(BaseModel):
    id: int
    service_name: Optional[str] = None
    opening_time: str
    closing_time: str
    is_active: bool
    discount: Optional[float] = None
    icon: Optional[str]


class ServiceDetail(BaseModel):
    id: int
    name: Optional[str]
    opening_time: str
    closing_time: str
    price_for_call: float
    is_active: bool
    discount: Optional[float] = 0
    discount_start_date: Optional[date] = None
    discount_end_date: Optional[date] = None
    cancellation_time: int
    description: Optional[str]
    icon: Optional[str]
    image: Optional[str]


class ServiceComment(BaseModel):
    id: int
    user_id: Optional[int]
    name: Optional[str]
    content: str
    rating: Optional[float]
    view_content: int
    mahalla_service_id: Optional[int]
    user_avatar: Optional[str]
    create_date: datetime
    is_author: bool


class ServiceCommentDetail(BaseModel):
    id: int
    user_id: Optional[int]
    content: str
    rating: Optional[float]
    view_content: int
    mahalla_service_id: Optional[int]


class CommentRequest(BaseModel):
    mahalla_service_id: int
    content: str
    rating: int
give me complete working best practised and full code, comment only where the updates are and short descrtiption of changes after code snippet