diff --git a/salon_management/README.rst b/salon_management/README.rst
new file mode 100644
index 000000000..0320cef4a
--- /dev/null
+++ b/salon_management/README.rst
@@ -0,0 +1,21 @@
+Beauty Spa Management v11
+=========================
+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.
+
+Features
+========
+* Online Booking Facility.
+* Accounting Facility.
+* Customer Notification Through Mail.
+* 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.
+
+
+Credits
+=======
+Developer : Avinash N K
+
diff --git a/salon_management/__init__.py b/salon_management/__init__.py
new file mode 100644
index 000000000..e997a6b55
--- /dev/null
+++ b/salon_management/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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 models
+from . import controllers
diff --git a/salon_management/__manifest__.py b/salon_management/__manifest__.py
new file mode 100644
index 000000000..a811ed90a
--- /dev/null
+++ b/salon_management/__manifest__.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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 .
+#
+###################################################################################
+{
+ 'name': 'Beauty Spa Management',
+ 'summary': """Beauty Parlour Management with Online Booking System""",
+ 'version': '11.0.1.0.0',
+ 'author': 'Cybrosys Techno Solutions',
+ 'website': "http://www.cybrosys.com",
+ 'company': 'Cybrosys Techno Solutions',
+ "category": "Industries",
+ 'depends': ['base', 'account_invoicing', 'mail', 'website'],
+ 'data': ['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',
+ 'security/ir.model.access.csv',
+ ],
+ 'demo': [
+ 'views/booking_demo.xml',
+ ],
+ 'images': ['static/description/banner.jpg'],
+ '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..b5ac72949
--- /dev/null
+++ b/salon_management/controllers/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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..8e91f01cf
--- /dev/null
+++ b/salon_management/controllers/main.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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_info = kwargs.get('check_date')
+ return date_info
+
+ @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()
+ if 'x' in post.keys():
+ date_check = post['x']
+ 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])])
+ date_check = str(date_check)
+ 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..d40f2480b
--- /dev/null
+++ b/salon_management/data/data_booking.xml
@@ -0,0 +1,11 @@
+
+
+
+ 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..16f56b685
--- /dev/null
+++ b/salon_management/data/data_chair.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Collection Today
+
+
+
+ 1
+ days
+ -1
+
+
+
+
+
+
\ No newline at end of file
diff --git a/salon_management/models/__init__.py b/salon_management/models/__init__.py
new file mode 100644
index 000000000..28ceb03f4
--- /dev/null
+++ b/salon_management/models/__init__.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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 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..e05912cd5
--- /dev/null
+++ b/salon_management/models/salon_booking.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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")
+
+ @api.multi
+ 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"
+
+ @api.multi
+ 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..fa8710dfa
--- /dev/null
+++ b/salon_management/models/salon_config.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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)
+
+ @api.multi
+ 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..ffd14acc0
--- /dev/null
+++ b/salon_management/models/salon_management.py
@@ -0,0 +1,417 @@
+# -*- coding: utf-8 -*-
+###################################################################################
+#
+# Cybrosys Technologies Pvt. Ltd.
+# Copyright (C) 2018-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)
+
+ @api.multi
+ def write(self, values):
+ if 'user_line' in values.keys():
+ if values['user_line']:
+ date_changer = []
+ for elements in values['user_line']:
+ if elements[1] is False:
+ date_changer.append(elements[2]['start_date'])
+ number = 0
+ num = 0
+ for records in self.user_line:
+ if records.end_date is False:
+ 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, val, uid, context=None):
+ salon_chair = self.pool.get('salon.chair')
+ for values in self.search(val, uid, []):
+ chair_obj = salon_chair.browse(val, uid, values, context=context)
+ 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 = 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=date.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(self.start_time, "%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 = 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=date.today(), required=True)
+ end_time = fields.Datetime(string="End time")
+ date = fields.Datetime(string="Date", required=True, default=date.today())
+ 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)
+ 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 = self.start_time
+ salon_start_time_date = salon_start_time[0:10]
+ self.write({'start_date_only': salon_start_time_date})
+
+ @api.multi
+ def action_view_invoice_salon(self):
+ imd = self.env['ir.model.data']
+ action = imd.xmlid_to_object('account.action_invoice_tree1')
+ list_view_id = imd.xmlid_to_res_id('account.invoice_tree')
+ form_view_id = imd.xmlid_to_res_id('account.invoice_form')
+ result = {
+ 'name': action.name,
+ 'help': action.help,
+ 'type': action.type,
+ 'views': [[form_view_id, 'form'], [list_view_id, 'tree'], [False, 'graph'], [False, 'kanban'],
+ [False, 'calendar'], [False, 'pivot']],
+ 'target': action.target,
+ 'context': action.context,
+ 'res_model': action.res_model,
+ 'res_id': self.invoice_number,
+ }
+ return result
+
+ @api.multi
+ 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)
+
+ @api.multi
+ def salon_confirm(self):
+ sequence_code = 'salon.order.sequence'
+ order_date = 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
+
+ @api.multi
+ def salon_validate(self):
+ self.validation_controller = True
+
+ @api.multi
+ 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)
+
+ @api.multi
+ 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)
+
+ @api.multi
+ 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']
+
+ @api.multi
+ def salon_invoice_create(self):
+ inv_obj = self.env['account.invoice']
+ inv_line_obj = self.env['account.invoice.line']
+ if self.partner_id:
+ supplier = self.partner_id
+ else:
+ supplier = self.partner_id.search([("name", "=", "Salon Default Customer")])
+ company_id = self.env['res.users'].browse(1).company_id
+ currency_salon = company_id.currency_id.id
+
+ inv_data = {
+ 'name': supplier.name,
+ 'reference': supplier.name,
+ 'account_id': supplier.property_account_payable_id.id,
+ 'partner_id': supplier.id,
+ 'currency_id': currency_salon,
+ 'journal_id': 1,
+ 'origin': self.name,
+ 'company_id': company_id.id,
+ }
+ inv_id = inv_obj.create(inv_data)
+ self.invoice_number = inv_id
+ 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))
+ inv_line_data = {
+ 'name': records.service_id.name,
+ 'account_id': income_account,
+ 'price_unit': records.price,
+ 'quantity': 1,
+ 'product_id': product_id.id,
+ 'invoice_id': inv_id.id,
+ }
+ inv_line_obj.create(inv_line_data)
+
+ imd = self.env['ir.model.data']
+ action = imd.xmlid_to_object('account.action_invoice_tree1')
+ list_view_id = imd.xmlid_to_res_id('account.invoice_tree')
+ form_view_id = imd.xmlid_to_res_id('account.invoice_form')
+
+ result = {
+ 'name': action.name,
+ 'help': action.help,
+ 'type': 'ir.actions.act_window',
+ 'views': [[list_view_id, 'tree'], [form_view_id, 'form'], [False, 'graph'], [False, 'kanban'],
+ [False, 'calendar'], [False, 'pivot']],
+ 'target': action.target,
+ 'context': action.context,
+ 'res_model': 'account.invoice',
+ }
+ if len(inv_id) > 1:
+ result['domain'] = "[('id','in',%s)]" % inv_id.ids
+ elif len(inv_id) == 1:
+ result['views'] = [(form_view_id, 'form')]
+ result['res_id'] = inv_id.ids[0]
+ else:
+ result = {'type': 'ir.actions.act_window_close'}
+ 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 = 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
+
+ @api.multi
+ 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'
+
+ name = fields.Char(string="Name", required=True, translate=True)
diff --git a/salon_management/security/ir.model.access.csv b/salon_management/security/ir.model.access.csv
new file mode 100644
index 000000000..ea59a21cd
--- /dev/null
+++ b/salon_management/security/ir.model.access.csv
@@ -0,0 +1,19 @@
+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,base.group_user,1,1,1,1
+view_salon_order_lines,view.salon.order.lines,model_salon_order_lines,base.group_user,1,1,1,1
+view_salon_stages,view.salon.stages,model_salon_stages,base.group_user,1,1,1,1
+view_salon_chair,view.salon.chair,model_salon_chair,base.group_user,1,1,1,1
+view_salon_service,view.salon.service,model_salon_service,base.group_user,1,1,1,1
+
+view_salon_order3,view.salon.order3,model_salon_order,base.group_public,1,1,1,1
+view_salon_order_lines3,view.salon.order3.lines,model_salon_order_lines,base.group_public,1,1,1,1
+view_salon_stages3,view.salon.stages3,model_salon_stages,base.group_public,1,1,1,1
+view_salon_chair3,view.salon.chair3,model_salon_chair,base.group_public,1,1,1,1
+view_salon_service3,view.salon.service3,model_salon_service,base.group_public,1,1,1,1
+view_salon_booking3,view.salon.booking3,model_salon_booking,base.group_public,1,1,1,1
+view_salon_working_hours3,view.salon.working.hours3,model_salon_working_hours,base.group_public,1,1,1,1
+view_salon_holiday3,view.salon.holiday3,model_salon_holiday,base.group_public,1,1,1,1
+view_res_partner3,view.res.partner3,model_res_partner,base.group_public,1,1,1,1
+view_res_users3,view.res.users3,model_res_users,base.group_public,1,1,1,1
+view_salon_chair_user3,view.salon.chair.user3,model_salon_chair_user,base.group_public,1,1,1,1
+view_salon_sequence_updater3,view.salon.sequence.updater3,model_salon_sequence_updater,base.group_public,1,1,1,1
\ No newline at end of file
diff --git a/salon_management/static/description/1.png b/salon_management/static/description/1.png
new file mode 100644
index 000000000..3314a8559
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..c43ac7252
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..a03f329d0
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..1a1899e8f
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..fec8fe9cc
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..8ffe9e92d
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..25bda67ee
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..006d6a885
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..cdd6130ff
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..30cc62a36
Binary files /dev/null and b/salon_management/static/description/9.png differ
diff --git a/salon_management/static/description/banner.jpg b/salon_management/static/description/banner.jpg
new file mode 100644
index 000000000..9cdd77b3b
Binary files /dev/null and b/salon_management/static/description/banner.jpg differ
diff --git a/salon_management/static/description/cybro_logo.png b/salon_management/static/description/cybro_logo.png
new file mode 100644
index 000000000..bb309114c
Binary files /dev/null and b/salon_management/static/description/cybro_logo.png differ
diff --git a/salon_management/static/description/icon.png b/salon_management/static/description/icon.png
new file mode 100644
index 000000000..ddb8bedeb
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..2ff049b70
--- /dev/null
+++ b/salon_management/static/description/index.html
@@ -0,0 +1,395 @@
+
+
+
+ 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.
+
+
+ Another advantage of having this Spa management system is your customers
+ who have done the online booking will get the booking information via email.
+ By using an app like this, you can enhance your business.
+
+
+
+
+
+
+
+
+
+ Features
+
+
+
+ Online Booking Facility.
+
+
+
+ Accounting Facility.
+
+
+ Customer Notification Through Mail..
+
+
+
+ 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 customers can book the service in two ways, direct booking and online booking. The chairs with Black font's are the only ones available for online booking and the White ones for direct booking.
+
+In Dashboard, you get the daily collection of that chair and when that chair will be free, in case if that chair is currently in use. Also you can see the active orders of that chair and if you click on it, that will directly take you to the active orders of that chair.
+
+If you want to change the settings of chair, you 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
+