diff --git a/salon_management/README.rst b/salon_management/README.rst
new file mode 100644
index 000000000..b38004b8f
--- /dev/null
+++ b/salon_management/README.rst
@@ -0,0 +1,40 @@
+Beauty Spa Management v14
+=========================
+This Spa management system developed by Cybrosys Techno Solutions helps
+your customers to do the online booking for using the service. This module
+integrates with other Odoo modules like accounting and website.
+
+Configuration
+=============
+* No additional configurations needed
+
+Company
+-------
+* `Cybrosys Techno Solutions `__
+
+Credits
+-------
+* Developer: AVINASH N K @ cybrosys, Contact: odoo@cybrosys.com
+ Version 13: Vaishnavi B@cybrosys,Contact: odoo@cybrosys.com
+ Version 14: JIBIN JAMES, Contact: odoo@cybrosys.com
+Contacts
+--------
+* Mail Contact : odoo@cybrosys.com
+* Website : https://cybrosys.com
+
+Bug Tracker
+-----------
+Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
+
+Maintainer
+==========
+.. image:: https://cybrosys.com/images/logo.png
+ :target: https://cybrosys.com
+
+This module is maintained by Cybrosys Technologies.
+
+For support and more information, please visit `Our Website `__
+
+Further information
+===================
+HTML Description: ``__
diff --git a/salon_management/__init__.py b/salon_management/__init__.py
new file mode 100644
index 000000000..b41ccadc2
--- /dev/null
+++ b/salon_management/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2019-TODAY Cybrosys Technologies().
+# Author: Avinash N K (odoo@cybrosys.com)
+#
+# This program is free software: you can modify
+# it under the terms of the GNU Affero General Public License (AGPL) as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###################################################################################
+
+from . import models
+from . import controllers
diff --git a/salon_management/__manifest__.py b/salon_management/__manifest__.py
new file mode 100644
index 000000000..3a5a44dfc
--- /dev/null
+++ b/salon_management/__manifest__.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2020-TODAY Cybrosys Technologies().
+#
+# This program is free software: you can modify
+# it under the terms of the GNU Affero General Public License (AGPL) as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###################################################################################
+{
+ 'name': 'Beauty Spa Management',
+ 'summary': """Beauty Parlour Management with Online Booking System""",
+ 'version': '14.0.1.0.0',
+ 'author': 'Cybrosys Techno Solutions',
+ 'website': "https://www.cybrosys.com",
+ 'company': 'Cybrosys Techno Solutions',
+ "category": "Industries",
+ 'depends': ['base','base_setup', 'account', 'mail', 'website'],
+ 'data': [
+ 'security/salon_security.xml',
+ 'security/ir.model.access.csv',
+ 'data/data_chair.xml',
+ 'data/data_booking.xml',
+ 'views/salon_holiday.xml',
+ 'views/js_view.xml',
+ 'views/salon_data.xml',
+ 'views/salon_management_chair.xml',
+ 'views/salon_management_services.xml',
+ 'views/salon_order_view.xml',
+ 'views/salon_management_dashboard.xml',
+ 'views/booking_backend.xml',
+ 'views/salon_email_template.xml',
+ 'views/salon_config.xml',
+ 'views/working_hours.xml',
+ 'templates/salon_booking_templates.xml',
+ ],
+ 'images': ['static/description/banner.png'],
+ 'license': 'AGPL-3',
+ 'installable': True,
+ 'application': True,
+}
diff --git a/salon_management/controllers/__init__.py b/salon_management/controllers/__init__.py
new file mode 100644
index 000000000..40a235a09
--- /dev/null
+++ b/salon_management/controllers/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2019-TODAY Cybrosys Technologies ().
+#
+# Author: AVINASH NK()
+#
+# This program is free software: you can modify
+# it under the terms of the GNU Affero General Public License (AGPL) as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###################################################################################
+
+from . import main
diff --git a/salon_management/controllers/main.py b/salon_management/controllers/main.py
new file mode 100644
index 000000000..83f1fe36a
--- /dev/null
+++ b/salon_management/controllers/main.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2019-TODAY Cybrosys Technologies ().
+#
+# Author: AVINASH NK()
+#
+# This program is free software: you can modify
+# it under the terms of the GNU Affero General Public License (AGPL) as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###################################################################################
+
+import json
+from datetime import datetime, date
+from odoo import http
+from odoo.http import request
+
+
+class SalonBookingWeb(http.Controller):
+
+ @http.route('/page/salon_details', csrf=False, type="http", methods=['POST', 'GET'], auth="public", website=True)
+ def salon_details(self, **kwargs):
+
+ name = kwargs['name']
+ dates = kwargs['date']
+ time = kwargs['time']
+ phone = kwargs['phone']
+ email = kwargs['email']
+ chair = kwargs['chair']
+ j = 0
+ service_list = []
+ while j < (int(kwargs['number'])):
+ item = "list_service["+str(j)+"][i]"
+ service_list.append(int(kwargs[item]))
+ j += 1
+ salon_service_obj = request.env['salon.service'].search([('id', 'in', service_list)])
+ dates_time = dates+" "+time+":00"
+ date_and_time = datetime.strptime(dates_time, '%m/%d/%Y %H:%M:%S')
+
+ salon_booking = request.env['salon.booking']
+ booking_data = {
+ 'name': name,
+ 'phone': phone,
+ 'time': date_and_time,
+ 'email': email,
+ 'chair_id': chair,
+ 'services': [(6, 0, [x.id for x in salon_service_obj])],
+ }
+ salon_booking.create(booking_data)
+ return json.dumps({'result': True})
+
+ @http.route('/page/salon_check_date', type='json', auth="public", website=True)
+ def salon_check(self, **kwargs):
+ date_check = str(kwargs.get('check_date'))
+ order_obj = request.env['salon.order'].search([('chair_id.active_booking_chairs', '=', True),
+ ('stage_id', 'in', [1, 2, 3]),
+ ('start_date_only', '=', datetime.strptime(date_check, '%m/%d/%Y').strftime('%Y-%m-%d'))])
+ order_details = {}
+ for orders in order_obj:
+ data = {
+ 'number': orders.id,
+ 'start_time_only': orders.start_time_only,
+ 'end_time_only': orders.end_time_only
+ }
+ if orders.chair_id.id not in order_details:
+ order_details[orders.chair_id.id] = {'name': orders.chair_id.name, 'orders': [data]}
+ else:
+ order_details[orders.chair_id.id]['orders'].append(data)
+ return order_details
+
+ @http.route('/page/salon_management.salon_booking_thank_you', type='http', auth="public", website=True)
+ def thank_you(self, **post):
+ return request.render('salon_management.salon_booking_thank_you', {})
+
+ @http.route('/page/salon_management/salon_booking_form', type='http', auth="public", website=True)
+ def chair_info(self, **post):
+ salon_service_obj = request.env['salon.service'].search([])
+ salon_working_hours_obj = request.env['salon.working.hours'].search([])
+ salon_holiday_obj = request.env['salon.holiday'].search([('holiday', '=', True)])
+ date_check = date.today()
+ chair_obj = request.env['salon.chair'].search([('active_booking_chairs', '=', True)])
+ order_obj = request.env['salon.order'].search([('chair_id.active_booking_chairs', '=', True),
+ ('stage_id', 'in', [1, 2, 3])])
+ order_obj = order_obj.search([('start_date_only', '=', date_check)])
+ return request.render('salon_management.salon_booking_form',
+ {'chair_details': chair_obj, 'order_details': order_obj,
+ 'salon_services': salon_service_obj, 'date_search': date_check,
+ 'holiday': salon_holiday_obj,
+ 'working_time': salon_working_hours_obj
+ })
diff --git a/salon_management/data/data_booking.xml b/salon_management/data/data_booking.xml
new file mode 100644
index 000000000..9731ebf47
--- /dev/null
+++ b/salon_management/data/data_booking.xml
@@ -0,0 +1,10 @@
+
+
+
+ Bookings
+ /page/salon_management/salon_booking_form
+
+ 80
+
+
+
\ No newline at end of file
diff --git a/salon_management/data/data_chair.xml b/salon_management/data/data_chair.xml
new file mode 100644
index 000000000..9a9c43ea5
--- /dev/null
+++ b/salon_management/data/data_chair.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ Collection Today
+
+
+ model.collection_today_updater()
+
+ 1
+ days
+ -1
+
+
+
+
+
+
\ No newline at end of file
diff --git a/salon_management/doc/RELEASE_NOTES.md b/salon_management/doc/RELEASE_NOTES.md
new file mode 100644
index 000000000..c3a0dceb3
--- /dev/null
+++ b/salon_management/doc/RELEASE_NOTES.md
@@ -0,0 +1,6 @@
+## Module
+
+#### 20.10.2020
+#### Version 14.0.1.0.0
+##### ADD
+- Initial Commit salon_management
diff --git a/salon_management/models/__init__.py b/salon_management/models/__init__.py
new file mode 100644
index 000000000..0c450ae0b
--- /dev/null
+++ b/salon_management/models/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+#
+# Copyright (C) 2019-TODAY Cybrosys Technologies().
+# Author: Avinash N k (odoo@cybrosys.com)
+#
+# You can modify it under the terms of the GNU AFFERO
+# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
+#
+# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+# (AGPL v3) along with this program.
+# If not, see .
+#
+#############################################################################
+from . import salon_management
+from . import salon_booking
+from . import salon_config
+
diff --git a/salon_management/models/salon_booking.py b/salon_management/models/salon_booking.py
new file mode 100644
index 000000000..effba3cdc
--- /dev/null
+++ b/salon_management/models/salon_booking.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2019-TODAY Cybrosys Technologies ().
+#
+# Author: AVINASH NK()
+#
+# This program is free software: you can modify
+# it under the terms of the GNU Affero General Public License (AGPL) as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###################################################################################
+
+from datetime import date
+from odoo import models, fields, api
+
+
+class SalonBookingBackend(models.Model):
+ _name = 'salon.booking'
+
+ name = fields.Char(string="Name")
+ state = fields.Selection([('draft', 'Draft'), ('approved', 'Approved'), ('rejected', 'Rejected')], default="draft")
+ time = fields.Datetime(string="Date")
+ phone = fields.Char(string="Phone")
+ email = fields.Char(string="E-Mail")
+ services = fields.Many2many('salon.service', string="Services")
+ chair_id = fields.Many2one('salon.chair', string="Chair")
+ company_id = fields.Many2one('res.company', 'Company',
+ default=lambda self: self.env['res.company'].browse(1))
+ lang = fields.Many2one('res.lang', 'Language',
+ default=lambda self: self.env['res.lang'].browse(1))
+
+ def all_salon_orders(self):
+ if self.time:
+ date_only = str(self.time)[0:10]
+ else:
+ date_only = date.today()
+ all_salon_service_obj = self.env['salon.order'].search([('chair_id', '=', self.chair_id.id),
+ ('start_date_only', '=', date_only)])
+ self.filtered_orders = [(6, 0, [x.id for x in all_salon_service_obj])]
+
+ filtered_orders = fields.Many2many('salon.order', string="Salon Orders", compute="all_salon_orders")
+
+ def booking_approve(self):
+ salon_order_obj = self.env['salon.order']
+ salon_service_obj = self.env['salon.order.lines']
+ order_data = {
+ 'customer_name': self.name,
+ 'chair_id': self.chair_id.id,
+ 'start_time': self.time,
+ 'date': date.today(),
+ 'stage_id': 1,
+ 'booking_identifier': True,
+ }
+ order = salon_order_obj.create(order_data)
+ for records in self.services:
+ service_data = {
+ 'service_id': records.id,
+ 'time_taken': records.time_taken,
+ 'price': records.price,
+ 'price_subtotal': records.price,
+ 'salon_order': order.id,
+ }
+ salon_service_obj.create(service_data)
+ template = self.env.ref('salon_management.salon_email_template_approved')
+ self.env['mail.template'].browse(template.id).send_mail(self.id)
+ self.state = "approved"
+
+ def booking_reject(self):
+ template = self.env.ref('salon_management.salon_email_template_rejected')
+ self.env['mail.template'].browse(template.id).send_mail(self.id)
+ self.state = "rejected"
+
diff --git a/salon_management/models/salon_config.py b/salon_management/models/salon_config.py
new file mode 100644
index 000000000..f3390069f
--- /dev/null
+++ b/salon_management/models/salon_config.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2019-TODAY Cybrosys Technologies ().
+#
+# Author: AVINASH NK()
+#
+# This program is free software: you can modify
+# it under the terms of the GNU Affero General Public License (AGPL) as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###################################################################################
+
+from odoo import models, fields, api
+
+
+class SalonWorkingHours(models.Model):
+ _name = 'salon.working.hours'
+
+ name = fields.Char(string="Name")
+ from_time = fields.Float(string="Starting Time")
+ to_time = fields.Float(string="Closing Time")
+
+
+class SalonHoliday(models.Model):
+ _name = 'salon.holiday'
+
+ name = fields.Char(string="Name")
+ holiday = fields.Boolean(string="Holiday")
+
+
+class ConfigurationSettings(models.TransientModel):
+ _inherit = 'res.config.settings'
+
+ @api.model
+ def booking_chairs(self):
+ return self.env['salon.chair'].search([('active_booking_chairs', '=', True)])
+
+ @api.model
+ def holidays(self):
+ return self.env['salon.holiday'].search([('holiday', '=', True)])
+
+ salon_booking_chairs = fields.Many2many('salon.chair', string="Booking Chairs", default=booking_chairs)
+ salon_holidays = fields.Many2many('salon.holiday', string="Holidays", default=holidays)
+
+ def execute(self):
+ salon_chair_obj = self.env['salon.chair'].search([])
+ book_chair = []
+ for chairs in self.salon_booking_chairs:
+ book_chair.append(chairs.id)
+ for records in salon_chair_obj:
+ if records.id in book_chair:
+ records.active_booking_chairs = True
+ else:
+ records.active_booking_chairs = False
+
+ salon_holiday_obj = self.env['salon.holiday'].search([])
+ holiday = []
+ for days in self.salon_holidays:
+ holiday.append(days.id)
+ for records in salon_holiday_obj:
+ if records.id in holiday:
+ records.holiday = True
+ else:
+ records.holiday = False
diff --git a/salon_management/models/salon_management.py b/salon_management/models/salon_management.py
new file mode 100644
index 000000000..0a4527ab3
--- /dev/null
+++ b/salon_management/models/salon_management.py
@@ -0,0 +1,411 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2019-TODAY Cybrosys Technologies ().
+#
+# Author: AVINASH NK()
+#
+# This program is free software: you can modify
+# it under the terms of the GNU Affero General Public License (AGPL) as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###################################################################################
+
+from datetime import date, datetime, timedelta
+from odoo import models, fields, api
+from odoo.tools.translate import _
+from odoo.exceptions import UserError, ValidationError
+
+
+class PartnerSalon(models.Model):
+ _inherit = 'res.partner'
+
+ partner_salon = fields.Boolean(string="Is a Salon Partner")
+
+
+class SequenceUpdaterSalon(models.Model):
+ _name = 'salon.sequence.updater'
+
+ sequence_salon = fields.Char(string="Salon Sequence")
+
+
+class UserSalon(models.Model):
+ _inherit = 'res.users'
+
+ user_salon_active = fields.Boolean(string="Active Salon Users")
+
+
+class SalonChair(models.Model):
+ _name = 'salon.chair'
+
+ name = fields.Char(string="Chair", required=True,
+ default=lambda self: self.env['salon.sequence.updater'].browse(1).sequence_salon or "Chair-1")
+ number_of_orders = fields.Integer(string="No.of Orders")
+ collection_today = fields.Float(string="Today's Collection")
+ user_of_chair = fields.Many2one('res.users', string="User", readonly=True,
+ help="You can select the user from the Users Tab."
+ "Last user from the Users Tab will be selected as the Current User.")
+ date = fields.Datetime(string="Date", readonly=True)
+ user_line = fields.One2many('salon.chair.user', 'salon_chair', string="Users")
+ total_time_taken_chair = fields.Float(string="Time Reserved(Hrs)")
+ active_booking_chairs = fields.Boolean(string="Active booking chairs")
+ chair_created_user = fields.Integer(string="Salon Chair Created User",
+ default=lambda self: self._uid)
+
+ @api.model
+ def create(self, values):
+ sequence_code = 'chair.sequence'
+ sequence_number = self.env['ir.sequence'].next_by_code(sequence_code)
+ self.env['salon.sequence.updater'].browse(1).write({'sequence_salon': sequence_number})
+ if 'user_line' in values.keys():
+ if values['user_line']:
+ date_changer = []
+ for elements in values['user_line']:
+ date_changer.append(elements[2]['start_date'])
+ number = 0
+ for elements in values['user_line']:
+ number += 1
+ if len(values['user_line']) == number:
+ break
+ elements[2]['end_date'] = date_changer[number]
+ values['user_of_chair'] = values['user_line'][len((values['user_line'])) - 1][2]['user_id']
+ values['date'] = values['user_line'][len((values['user_line'])) - 1][2]['start_date']
+ return super(SalonChair, self).create(values)
+
+ def write(self, values):
+ if 'user_line' in values.keys():
+ if values['user_line']:
+ date_changer = []
+ for elements in values['user_line']:
+ if str(elements[1]).startswith('v'):
+ date_changer.append(elements[2]['start_date'])
+ number = 0
+ num = 0
+ for records in self.user_line:
+ if records.end_date is False:
+ if date_changer:
+ records.end_date = date_changer[0]
+ for elements in values['user_line']:
+ number += 1
+ if elements[2] is not False:
+ num += 1
+ if len(values['user_line']) == number:
+ break
+ elements[2]['end_date'] = date_changer[num]
+ values['user_of_chair'] = values['user_line'][len((values['user_line'])) - 1][2]['user_id']
+ values['date'] = values['user_line'][len((values['user_line'])) - 1][2]['start_date']
+ return super(SalonChair, self).write(values)
+
+ def collection_today_updater(self):
+ salon_chair = self.env['salon.chair']
+ for values in self.search([]):
+ chair_obj = salon_chair.browse(values.ids)
+ invoiced_records = chair_obj.env['salon.order'].search([('stage_id', 'in', [3, 4]),
+ ('chair_id', '=', chair_obj.id)])
+ total = 0
+ for rows in invoiced_records:
+ invoiced_date = str(rows.date)
+ invoiced_date = invoiced_date[0:10]
+ if invoiced_date == str(date.today()):
+ total = total + rows.price_subtotal
+ chair_obj.collection_today = total
+
+
+class SalonChairUserLines(models.Model):
+ _name = 'salon.chair.user'
+
+ read_only_checker = fields.Boolean(string="Checker", default=False)
+ user_id = fields.Many2one('res.users', string="User", required=True)
+ start_date = fields.Datetime(string="Start Date", default=datetime.today(), required=True)
+ end_date = fields.Datetime(string="End Date", readonly=True, default=False)
+ salon_chair = fields.Many2one('salon.chair', string="Chair", required=True, ondelete='cascade',
+ index=True, copy=False)
+
+ @api.model
+ def create(self, val):
+ chairs = self.env['salon.chair'].search([])
+ all_active_users = []
+ for records in chairs:
+ if records.user_of_chair:
+ all_active_users.append(records.user_of_chair.id)
+ records.user_of_chair.write({'user_salon_active': True})
+ users = self.env['res.users'].search([('id', 'not in', all_active_users)])
+ for records in users:
+ records.write({'user_salon_active': False})
+ val['read_only_checker'] = True
+ return super(SalonChairUserLines, self).create(val)
+
+
+class SalonOrder(models.Model):
+ _name = 'salon.order'
+
+ @api.depends('order_line.price_subtotal')
+ def sub_total_update(self):
+ for order in self:
+ amount_untaxed = 0.0
+ total_time_taken = 0.0
+ for line in order.order_line:
+ amount_untaxed += line.price_subtotal
+ total_time_taken += line.time_taken
+ self.price_subtotal = amount_untaxed
+ time_takes = total_time_taken
+ hours = int(time_takes)
+ minutes = (time_takes - hours) * 60
+ start_time_store = datetime.strptime(str(self.start_time).split(".")[0], "%Y-%m-%d %H:%M:%S")
+ self.write({'end_time': start_time_store + timedelta(hours=hours, minutes=minutes),
+ 'time_taken_total': total_time_taken})
+ if self.end_time:
+ self.write({'end_time_only': str(self.end_time)[11:16]})
+ if self.start_time:
+ salon_start_time = str(self.start_time)
+ salon_start_time_date = salon_start_time[0:10]
+ self.write({'start_date_only': salon_start_time_date})
+ self.write({'start_time_only': str(self.start_time)[11:16]})
+
+ name = fields.Char(string='Salon', required=True, copy=False, readonly=True,
+ default='Draft Salon Order')
+ start_time = fields.Datetime(string="Start time", default=datetime.now(), required=True)
+ end_time = fields.Datetime(string="End time")
+ date = fields.Datetime(string="Date", required=True, default=datetime.now())
+ color = fields.Integer(string="Colour", default=6)
+ partner_id = fields.Many2one('res.partner', string="Customer", required=False,
+ help="If the customer is a regular customer, "
+ "then you can add the customer in your database")
+ customer_name = fields.Char(string="Name", required=True)
+ amount = fields.Float(string="Amount")
+ chair_id = fields.Many2one('salon.chair', string="Chair", required=True)
+ price_subtotal = fields.Float(string='Total', compute='sub_total_update', readonly=True, store=True)
+ time_taken_total = fields.Float(string="Total time taken")
+ note = fields.Text('Terms and conditions')
+ order_line = fields.One2many('salon.order.lines', 'salon_order', string="Order Lines")
+ stage_id = fields.Many2one('salon.stages', string="Stages", default=1, group_expand='_read_group_stage_ids')
+ inv_stage_identifier = fields.Boolean(string="Stage Identifier")
+ invoice_number = fields.Integer(string="Invoice Number")
+ validation_controller = fields.Boolean(string="Validation controller", default=False)
+ start_date_only = fields.Date(string="Date Only")
+ booking_identifier = fields.Boolean(string="Booking Identifier")
+ start_time_only = fields.Char(string="Start Time Only")
+ end_time_only = fields.Char(string="End Time Only")
+ chair_user = fields.Many2one('res.users', string="Chair User")
+ salon_order_created_user = fields.Integer(string="Salon Order Created User",
+ default=lambda self: self._uid)
+
+ @api.onchange('start_time')
+ def start_date_change(self):
+ salon_start_time = str(self.start_time)
+ salon_start_time_date = salon_start_time[0:10]
+ self.write({'start_date_only': salon_start_time_date})
+
+ count = fields.Integer(string='Delivery Orders',
+ compute='_compute_invoice_saloon_ids')
+
+ def _compute_invoice_saloon_ids(self):
+ """getting saloon invoice count"""
+ for orders in self:
+ orders.count = self.env['account.move']. \
+ search_count([('invoice_origin', '=', self.name)])
+
+
+ # if orders.count != 0:
+ # orders.inv_stage_identifier = True
+ # orders.stage_id = 3
+
+ def action_view_invoice_salon(self):
+
+ return {
+ 'name': 'Invoices',
+ 'domain': [('invoice_origin', '=', self.name)],
+ 'res_model': 'account.move',
+ 'view_id': False,
+ 'view_mode': 'tree,form',
+ 'type': 'ir.actions.act_window',
+ }
+
+ @api.model
+ def _read_group_stage_ids(self, stages, domain, order):
+ stage_ids = self.env['salon.stages'].search([])
+ return stage_ids
+
+ def write(self, values):
+ if 'stage_id' in values.keys():
+ if self.stage_id.id == 3 and values['stage_id'] != 4:
+ raise ValidationError(_("You can't perform that move !"))
+ if self.stage_id.id == 1 and values['stage_id'] not in [2, 5]:
+ raise ValidationError(_("You can't perform that move!"))
+ if self.stage_id.id == 4:
+ raise ValidationError(_("You can't move a salon order from closed stage !"))
+ if self.stage_id.id == 5:
+ raise ValidationError(_("You can't move a salon order from cancel stage !"))
+ if self.stage_id.id == 2 and (values['stage_id'] == 1 or values['stage_id'] == 4):
+ raise ValidationError(_("You can't perform that move !"))
+ if self.stage_id.id == 2 and values['stage_id'] == 3 and self.inv_stage_identifier is False:
+ self.salon_invoice_create()
+
+ if 'stage_id' in values.keys() and self.name == "Draft Salon Order":
+ if values['stage_id'] == 2:
+ self.salon_confirm()
+ return super(SalonOrder, self).write(values)
+
+ def salon_confirm(self):
+ sequence_code = 'salon.order.sequence'
+ order_date = str(self.date)
+ order_date = order_date[0:10]
+ self.name = self.env['ir.sequence'].with_context(ir_sequence_date=order_date).next_by_code(sequence_code)
+ if self.partner_id:
+ self.partner_id.partner_salon = True
+ self.stage_id = 2
+ self.chair_id.number_of_orders = len(self.env['salon.order'].search([("chair_id", "=", self.chair_id.id),
+ ("stage_id", "in", [2, 3])]))
+ self.chair_id.total_time_taken_chair = (self.chair_id.total_time_taken_chair + self.time_taken_total)
+ self.chair_user = self.chair_id.user_of_chair
+
+ def salon_validate(self):
+ self.validation_controller = True
+
+ def salon_close(self):
+
+ self.stage_id = 4
+ self.chair_id.number_of_orders = len(self.env['salon.order'].search([("chair_id", "=", self.chair_id.id),
+ ("stage_id", "in", [2, 3])]))
+ self.chair_id.total_time_taken_chair = (self.chair_id.total_time_taken_chair - self.time_taken_total)
+
+ def salon_cancel(self):
+ self.stage_id = 5
+ self.chair_id.number_of_orders = len(self.env['salon.order'].search([("chair_id", "=", self.chair_id.id),
+ ("stage_id", "in", [2, 3])]))
+ if self.stage_id.id != 1:
+ self.chair_id.total_time_taken_chair = (self.chair_id.total_time_taken_chair - self.time_taken_total)
+
+ def button_total_update(self):
+ for order in self:
+ amount_untaxed = 0.0
+ for line in order.order_line:
+ amount_untaxed += line.price_subtotal
+ order.price_subtotal = amount_untaxed
+
+
+ @api.onchange('chair_id')
+ def onchange_chair(self):
+ if 'active_id' in self._context.keys():
+ self.chair_id = self._context['active_id']
+
+ def salon_invoice_create(self):
+ if self.partner_id:
+ supplier = self.partner_id
+ else:
+ supplier = self.partner_id.search([("name", "=", "Salon Default Customer")])
+ lines = []
+ product_id = self.env['product.product'].search([("name", "=", "Salon Service")])
+ for records in self.order_line:
+ if product_id.property_account_income_id.id:
+ income_account = product_id.property_account_income_id.id
+ elif product_id.categ_id.property_account_income_categ_id.id:
+ income_account = product_id.categ_id.property_account_income_categ_id.id
+ else:
+ raise UserError(_('Please define income account for this product: "%s" (id:%d).') % (product_id.name,
+ product_id.id))
+
+ value = (0, 0, {
+ 'name': records.service_id.name,
+ 'account_id': income_account,
+ 'price_unit': records.price,
+ 'quantity': 1,
+ 'product_id': product_id.id,
+ })
+ lines.append(value)
+
+ invoice_line = {
+ 'move_type': 'out_invoice',
+ 'partner_id': supplier.id,
+ 'l10n_in_gst_treatment': 'consumer',
+ 'invoice_user_id': self.env.user.id,
+ 'invoice_origin': self.name,
+ 'invoice_line_ids': lines,
+ }
+ inv = self.env['account.move'].create(invoice_line)
+
+
+ imd = self.env['ir.model.data']
+
+ action = imd.xmlid_to_object('account.action_move_out_invoice_type')
+ result = {
+ 'name': action.name,
+ 'type': 'ir.actions.act_window',
+ 'views': [[False, 'form']],
+ 'target': 'current',
+ 'res_id':inv.id,
+ 'res_model': 'account.move',
+ }
+
+ self.inv_stage_identifier = True
+ self.stage_id = 3
+ invoiced_records = self.env['salon.order'].search([('stage_id', 'in', [3, 4]),
+ ('chair_id', '=', self.chair_id.id)])
+ total = 0
+ for rows in invoiced_records:
+ invoiced_date = str(rows.date)
+ invoiced_date = invoiced_date[0:10]
+ if invoiced_date == str(date.today()):
+ total = total + rows.price_subtotal
+ self.chair_id.collection_today = total
+ self.chair_id.number_of_orders = len(self.env['salon.order'].search([("chair_id", "=", self.chair_id.id),
+ ("stage_id", "in", [2, 3])]))
+
+ return result
+
+ def unlink(self):
+ for order in self:
+ if order.stage_id.id == 3 or order.stage_id.id == 4:
+ raise UserError(_("You can't delete an invoiced salon order!"))
+ return super(SalonOrder, self).unlink()
+
+
+class SalonServices(models.Model):
+ _name = 'salon.service'
+
+ name = fields.Char(string="Name")
+ price = fields.Float(string="Price")
+ time_taken = fields.Float(string="Time Taken", help="Approximate time taken for this service in Hours")
+
+
+class SalonOrderLine(models.Model):
+ _name = 'salon.order.lines'
+
+ service_id = fields.Many2one('salon.service', string="Service")
+ price = fields.Float(string="Price")
+ salon_order = fields.Many2one('salon.order', string="Salon Order", required=True, ondelete='cascade',
+ index=True, copy=False)
+ price_subtotal = fields.Float(string='Subtotal')
+ time_taken = fields.Float(string='Time Taken')
+
+ @api.onchange('service_id')
+ def onchange_service(self):
+ self.price = self.service_id.price
+ self.price_subtotal = self.service_id.price
+ self.time_taken = self.service_id.time_taken
+
+ @api.onchange('price')
+ def onchange_price(self):
+ self.price_subtotal = self.price
+
+ @api.onchange('price_subtotal')
+ def onchange_subtotal(self):
+ self.price = self.price_subtotal
+
+
+class SalonStages(models.Model):
+ _name = 'salon.stages'
+ _order = "sequence"
+
+ name = fields.Char(string="Name", required=True, translate=True)
+ sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.")
diff --git a/salon_management/security/ir.model.access.csv b/salon_management/security/ir.model.access.csv
new file mode 100644
index 000000000..c549b8da8
--- /dev/null
+++ b/salon_management/security/ir.model.access.csv
@@ -0,0 +1,32 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+view_salon_order,view.salon.order,model_salon_order,salon_management.group_salon_user,1,1,1,0
+view_salon_order_lines,view.salon.order.lines,model_salon_order_lines,salon_management.group_salon_user,1,1,1,0
+view_salon_stages,view.salon.stages,model_salon_stages,salon_management.group_salon_user,1,1,1,0
+view_salon_chair,view.salon.chair,model_salon_chair,salon_management.group_salon_user,1,1,0,0
+view_salon_service,view.salon.service,model_salon_service,salon_management.group_salon_user,1,1,0,0
+
+view_salon_order2,view.salon.order3,model_salon_order,salon_management.group_salon_manager,1,1,1,1
+view_salon_order_lines2,view.salon.order3.lines,model_salon_order_lines,salon_management.group_salon_manager,1,1,1,1
+view_salon_stages2,view.salon.stages3,model_salon_stages,salon_management.group_salon_manager,1,1,1,1
+view_salon_chair2,view.salon.chair3,model_salon_chair,salon_management.group_salon_manager,1,1,1,1
+view_salon_service2,view.salon.service3,model_salon_service,salon_management.group_salon_manager,1,1,1,1
+view_salon_booking2,view.salon.booking3,model_salon_booking,salon_management.group_salon_manager,1,1,1,1
+view_salon_working_hours2,view.salon.working.hours3,model_salon_working_hours,salon_management.group_salon_manager,1,1,1,1
+view_salon_holiday2,view.salon.holiday3,model_salon_holiday,salon_management.group_salon_manager,1,1,1,1
+view_res_partner2,view.res.partner3,model_res_partner,salon_management.group_salon_manager,1,1,1,1
+view_res_users2,view.res.users3,model_res_users,salon_management.group_salon_manager,1,1,1,1
+view_salon_chair_user2,view.salon.chair.user3,model_salon_chair_user,salon_management.group_salon_manager,1,1,1,1
+view_salon_sequence_updater2,view.salon.sequence.updater3,model_salon_sequence_updater,salon_management.group_salon_manager,1,1,1,1
+
+view_salon_order3,view.salon.order3,model_salon_order,base.group_public,1,0,0,0
+view_salon_order_lines3,view.salon.order3.lines,model_salon_order_lines,base.group_public,1,0,0,0
+view_salon_stages3,view.salon.stages3,model_salon_stages,base.group_public,1,0,0,0
+view_salon_chair3,view.salon.chair3,model_salon_chair,base.group_public,1,0,0,0
+view_salon_service3,view.salon.service3,model_salon_service,base.group_public,1,0,0,0
+view_salon_booking3,view.salon.booking3,model_salon_booking,base.group_public,1,1,1,0
+view_salon_working_hours3,view.salon.working.hours3,model_salon_working_hours,base.group_public,1,0,0,0
+view_salon_holiday3,view.salon.holiday3,model_salon_holiday,base.group_public,1,0,0,0
+view_res_partner3,view.res.partner3,model_res_partner,base.group_public,1,0,0,0
+view_res_users3,view.res.users3,model_res_users,base.group_public,1,0,0,0
+view_salon_chair_user3,view.salon.chair.user3,model_salon_chair_user,base.group_public,1,0,0,0
+view_salon_sequence_updater3,view.salon.sequence.updater3,model_salon_sequence_updater,base.group_public,1,0,0,0
diff --git a/salon_management/security/salon_security.xml b/salon_management/security/salon_security.xml
new file mode 100644
index 000000000..15301ccb3
--- /dev/null
+++ b/salon_management/security/salon_security.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Salon Management
+ Helps you handle your salon needs
+ 5
+
+
+
+ Chair User
+
+
+
+
+
+ Salon Manager
+
+
+
+
+
+
+
diff --git a/salon_management/static/description/1.png b/salon_management/static/description/1.png
new file mode 100644
index 000000000..858734349
Binary files /dev/null and b/salon_management/static/description/1.png differ
diff --git a/salon_management/static/description/10.png b/salon_management/static/description/10.png
new file mode 100644
index 000000000..0ba61e6e0
Binary files /dev/null and b/salon_management/static/description/10.png differ
diff --git a/salon_management/static/description/2.png b/salon_management/static/description/2.png
new file mode 100644
index 000000000..578302093
Binary files /dev/null and b/salon_management/static/description/2.png differ
diff --git a/salon_management/static/description/3.png b/salon_management/static/description/3.png
new file mode 100644
index 000000000..8c1c30931
Binary files /dev/null and b/salon_management/static/description/3.png differ
diff --git a/salon_management/static/description/4.png b/salon_management/static/description/4.png
new file mode 100644
index 000000000..a2a0be8e8
Binary files /dev/null and b/salon_management/static/description/4.png differ
diff --git a/salon_management/static/description/5.png b/salon_management/static/description/5.png
new file mode 100644
index 000000000..85b3d57b7
Binary files /dev/null and b/salon_management/static/description/5.png differ
diff --git a/salon_management/static/description/6.png b/salon_management/static/description/6.png
new file mode 100644
index 000000000..f2b6de0ea
Binary files /dev/null and b/salon_management/static/description/6.png differ
diff --git a/salon_management/static/description/7.png b/salon_management/static/description/7.png
new file mode 100644
index 000000000..dd4e70bc3
Binary files /dev/null and b/salon_management/static/description/7.png differ
diff --git a/salon_management/static/description/8.png b/salon_management/static/description/8.png
new file mode 100644
index 000000000..0c0469a32
Binary files /dev/null and b/salon_management/static/description/8.png differ
diff --git a/salon_management/static/description/9.png b/salon_management/static/description/9.png
new file mode 100644
index 000000000..05369c69d
Binary files /dev/null and b/salon_management/static/description/9.png differ
diff --git a/salon_management/static/description/banner.png b/salon_management/static/description/banner.png
new file mode 100644
index 000000000..a78b5cca0
Binary files /dev/null and b/salon_management/static/description/banner.png differ
diff --git a/salon_management/static/description/icon.png b/salon_management/static/description/icon.png
new file mode 100644
index 000000000..d8b55f54d
Binary files /dev/null and b/salon_management/static/description/icon.png differ
diff --git a/salon_management/static/description/index.html b/salon_management/static/description/index.html
new file mode 100644
index 000000000..8e70c79f2
--- /dev/null
+++ b/salon_management/static/description/index.html
@@ -0,0 +1,443 @@
+
+
+
+ Cybrosys Techno Solution’s Spa Management System envisions in giving a complete
+ new experience to client in availing their spa services.
+ The Spa Management System helps the customer in processing their online booking
+ for spa services. The module comes integrated with Odoo Accounting and Website, offering
+ comprehensive plethora of functions.
+
+
+ One of the significant advantage of using the Spa Management System is that, it offers your
+ customer their booking information via email. The client is equipped with the status of
+ their booking, enhancing the customary experience and brand building of your business.
+
+
+
+
+
+
+
+
+
+ Features
+
+
+
+ Online booking facility.
+
+
+
+ Accounting facility.
+
+
+ Customer notification via email.
+
+
+
+ User interactive dashboard.
+
+
+ Customer can view the available chairs and order details.
+
+
+ Different access levels for users and administrator.
+
+
+ Track the chair user by date.
+
+
+
+
+
+
+
+ Dashboard
+
+
+ Dashboard view is shown in the below image.
+ Your customer can book the service in two ways, either via direct booking or online booking.
+ The chairs with Black font's denote the ones available for online booking and the White ones
+ for direct booking. From dashboard, one get all information about the chair, whether the
+ particular chair is free or is in currently use. Also one can see the
+ active orders of that chair. If the end user clicks in , it will directly navigate to the
+ active orders of that chair. In case, if the end user want to change the
+ settings of chair, they can just click the settings button in the dashboard.
+
+
+
+
+ Chair
+
+
+ Go to Salon --> Chair
+
+ Here you can create and edit details of each chair. You can assign a user to the chair by adding new user to the users tab in the form view of chair(Check the image shown below). The last added user will be turned to the current user of the chair. Using this tab, you can track which user is active on the Chair on a particular date or time.
+
+
+
+
+
+ Services
+
+
Go to Salon --> Services
+
+ List of services provided by the Spa. You can create and edit services here.
+
+
+
+
+
+ Salon Orders
+
+
Go to Salon --> Salon Orders
+ Here you have all the orders in the Spa.
+
+
+
+
+ Salon Order Form View
+
+
Go to Salon --> Salon Orders --> Form View
+ Form view of the salon order is shown in the image. You will get the corresponding invoice details of the order by clicking the Invoice button in the upper right side of the form view.
+
+
+
+
+ Booking
+
+
Go to Salon --> Bookings
+ Here you can see all the online bookings. You can either approve a booking or reject a booking. In both cases the notification is send to the customer mail address. The approved bookings will be changed to salon orders.
+
+
+
+
+ Booking Form View
+
+
Go to Salon --> Bookings --> Form View
+ Here you can see all existing salon orders on the corresponding date on that chair. After checking the time availability, you can decide to approve/reject the booking.
+
+
+
+
+ Settings
+
+
Go to Salon --> Configuration --> Settings
+ Here you can assign the chairs for booking purpose. Only the assigned chairs can be booked by a customer through online. Also you can select the holidays here
+