diff --git a/fleet_car_workshop/README.rst b/fleet_car_workshop/README.rst new file mode 100644 index 000000000..a72f187e2 --- /dev/null +++ b/fleet_car_workshop/README.rst @@ -0,0 +1,18 @@ +Car Workshop v10 +================ +Car Workshop Management helps to manage automobile workshop with +great ease. Keep track of everything, like vehicle owner details, +Works assigned, Bill details of service provided, etc. + +Features +======== +* User Friendly Interface. +* Effective Time management. +* Separate Journal Configuration. +* Integrated with Accounting. +* High Scalability. + + .. note:: + + # Its Working Domain Based on Project App. + # Mapped Fleet for Easy Method. diff --git a/fleet_car_workshop/__init__.py b/fleet_car_workshop/__init__.py new file mode 100644 index 000000000..9e0710ae3 --- /dev/null +++ b/fleet_car_workshop/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Nilmar Shereef() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## +import models + diff --git a/fleet_car_workshop/__manifest__.py b/fleet_car_workshop/__manifest__.py new file mode 100644 index 000000000..de55dc7ec --- /dev/null +++ b/fleet_car_workshop/__manifest__.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Nilmar Shereef() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## +{ + 'name': 'Car Workshop', + 'version': '10.0.1.0.0', + 'summary': 'Complete Vehicle Workshop Operations & Reports', + 'description': 'Vehicle workshop operations & Its reports', + 'category': 'Industries', + 'author': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'company': 'Cybrosys Techno Solutions', + 'depends': [ + 'base', + 'fleet', + 'account_accountant', + ], + 'data': [ + 'views/worksheet_views.xml', + 'views/car_dashboard.xml', + 'views/timesheet_view.xml', + 'views/worksheet_stages.xml', + 'views/vehicle.xml', + 'views/report.xml', + 'views/config_setting.xml', + 'views/workshop_data.xml', + 'security/workshop_security.xml', + 'security/ir.model.access.csv', + ], + 'images': ['static/description/banner.jpg'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/fleet_car_workshop/models/__init__.py b/fleet_car_workshop/models/__init__.py new file mode 100644 index 000000000..b020ac10b --- /dev/null +++ b/fleet_car_workshop/models/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Nilmar Shereef() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## +import car_workshop +import timesheet +import dashboard +import config diff --git a/fleet_car_workshop/models/car_workshop.py b/fleet_car_workshop/models/car_workshop.py new file mode 100644 index 000000000..2a3c99e14 --- /dev/null +++ b/fleet_car_workshop/models/car_workshop.py @@ -0,0 +1,374 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Nilmar Shereef() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## +from datetime import date +from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError +from odoo import models, api, fields, _ + + +class CarWorkshop(models.Model): + _name = 'car.workshop' + _description = "Car Workshop" + _inherit = ['mail.thread'] + + def _get_default_partner(self): + if 'default_vehicle_id' in self.env.context: + default_vehicle_id = self.env['car.car'].browse(self.env.context['default_vehicle_id']) + return default_vehicle_id.exists().partner_id + + name = fields.Char(string='Title', track_visibility='onchange', required=True) + vehicle_id = fields.Many2one('car.car', string='Vehicle', + default=lambda self: self.env.context.get('default_vehicle_id'), track_visibility='onchange') + user_id = fields.Many2one('res.users', string='Assigned to', select=True, default=lambda self: self.env.uid) + active = fields.Boolean(string='Active', default=True) + partner_id = fields.Many2one('res.partner', string='Customer',default=_get_default_partner) + priority = fields.Selection([('0', 'Normal'), ('1', 'High')], 'Priority', select=True, default='0') + description = fields.Html(string='Description') + sequence = fields.Integer(string='Sequence', select=True,default=10, help="Gives the sequence order when displaying a list of tasks.") + tag_ids = fields.Many2many('worksheet.tags', string='Tags') + kanban_state = fields.Selection( + [('normal', 'In Progress'), ('done', 'Ready for next stage'), ('blocked', 'Blocked')], 'Kanban State', + help="A task's kanban state indicates special situations affecting it:\n" + " * Normal is the default situation\n" + " * Blocked indicates something is preventing the progress of this task\n" + " * Ready for next stage indicates the task is ready to be pulled to the next stage", + required=True, track_visibility='onchange',default='normal', copy=False) + create_date = fields.Datetime(string='Create Date', readonly=True, select=True) + write_date = fields.Datetime(string='Last Modification Date', readonly=True, select=True) + date_start = fields.Datetime(string='Starting Date', default=fields.datetime.now(),select=True, copy=False) + date_end = fields.Datetime(string='Ending Date', select=True, copy=False) + date_assign = fields.Datetime(string='Assigning Date', select=True, copy=False, readonly=True) + date_deadline = fields.Datetime(string='Deadline', select=True, copy=False) + progress = fields.Integer(string="Working Time Progress(%)", copy=False, readonly=True) + date_last_stage_update = fields.Datetime(string='Last Stage Update', select=True, default=fields.datetime.now(),copy=False, readonly=True) + id = fields.Integer('ID', readonly=True) + company_id = fields.Many2many('res.company', string='Company Name', default=lambda self: self.env['res.company']._company_default_get('car.workshop')) + color = fields.Integer(string='Color Index') + stage_id = fields.Many2one('worksheet.stages', string='Stage', track_visibility='onchange', copy=False) + state = fields.Selection([ + ('waiting', 'Ready'), + ('workshop_create_invoices', 'Invoiced'), + ('cancel', 'Invoice Canceled'), + ], string='Status', readonly=True, default='waiting', track_visibility='onchange', select=True) + attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=lambda self: [('res_model', '=', self._name)], + auto_join=True, string='Attachments') + displayed_image_id = fields.Many2one('ir.attachment', + domain="[('res_model', '=', 'car.workshop'), ('res_id', '=', id)," + "\ ('mimetype', 'ilike', 'image')]", + string='Displayed Image') + planned_works = fields.One2many('planned.work', 'work_id', string='Planned/Ordered Works') + works_done = fields.One2many('planned.work', 'work_id', string='Work Done', domain=[('completed', '=', True)]) + materials_used = fields.One2many('material.used', 'material_id', string='Materials Used') + remaining_hour = fields.Float(string='Remaining Hour', readonly=True, compute="hours_left") + effective_hour = fields.Float(string='Hours Spent', readonly=True, compute="hours_spent") + amount_total = fields.Float(string='Total Amount', readonly=True, compute="amount_total1") + + def _get_default_stages(self, cr, uid, context=None): + """ Gives default stage_id """ + if context is None: + context = {} + default_vehicle_id = context.get('default_vehicle_id') + if not default_vehicle_id: + return False + return self.find_stage(cr, uid, [], default_vehicle_id, [('fold', '=', False)], context=context) + + _defaults = { + 'stage_id': _get_default_stages, + } + + @api.depends('planned_works.work_cost', 'materials_used.price') + def amount_total1(self): + for records in self: + for hour in records: + amount_totall = 0.0 + for line in hour.planned_works: + amount_totall += line.work_cost + for line2 in hour.materials_used: + amount_totall += line2.price + records.amount_total = amount_totall + + @api.multi + def cancel(self): + self.state = 'cancel' + + @api.multi + def workshop_create_invoices(self): + + self.state = 'workshop_create_invoices' + inv_obj = self.env['account.invoice'] + inv_line_obj = self.env['account.invoice.line'] + customer = self.partner_id + if not customer.name: + raise UserError( + _( + 'Please select a Customer.')) + + company_id = self.env['res.users'].browse(1).company_id + currency_value = company_id.currency_id.id + self.ensure_one() + ir_values = self.env['ir.values'] + journal_id = ir_values.get_default('workshop.config.setting', 'invoice_journal_type') + if not journal_id: + journal_id = 1 + + inv_data = { + 'name': customer.name, + 'reference': customer.name, + 'account_id': customer.property_account_payable_id.id, + 'partner_id': customer.id, + 'currency_id': currency_value, + 'journal_id': journal_id, + 'origin': self.name, + 'company_id': company_id.id, + } + inv_id = inv_obj.create(inv_data) + for records in self.planned_works: + if records.planned_work.id : + income_account = records.planned_work.property_account_income_id.id + if not income_account: + raise UserError(_('There is no income account defined for this product: "%s".') % + (records.planned_work.name,)) + + inv_line_data = { + 'name': records.planned_work.name, + 'account_id': income_account, + 'price_unit': records.work_cost, + 'quantity': 1, + 'product_id': records.planned_work.id, + 'invoice_id': inv_id.id, + } + inv_line_obj.create(inv_line_data) + + for records in self.materials_used: + if records.material.id : + income_account = records.material.property_account_income_id.id + if not income_account: + raise UserError(_('There is no income account defined for this product: "%s".') % + (records.material.name,)) + + inv_line_data = { + 'name': records.material.name, + 'account_id': records.material.property_account_income_id.id, + 'price_unit': records.price, + 'quantity': records.amount, + 'product_id': records.material.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'} + invoiced_records = self.env['car.workshop'] + + 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 + return result + + @api.depends('works_done.duration') + def hours_spent(self): + for hour in self: + effective_hour = 0.0 + for line in hour.works_done: + effective_hour += line.duration + self.effective_hour = effective_hour + + @api.depends('planned_works.time_spent') + def hours_left(self): + for hour in self: + remaining_hour = 0.0 + for line in hour.planned_works: + remaining_hour += line.time_spent + self.remaining_hour = remaining_hour-self.effective_hour + + def process_demo_scheduler_queue(self): + obj = self.env['car.workshop'] + obj1 = obj.search([]) + now = fields.Datetime.from_string(fields.Datetime.now()) + for obj2 in obj1: + obj3 = obj2 + if obj3.stage_id.name != 'Done' and obj3.stage_id.name != 'Cancelled' and obj3.stage_id.name != 'Verified': + end_date = fields.Datetime.from_string(obj3.date_deadline) + start_date = fields.Datetime.from_string(obj3.date_assign) + if obj3.date_deadline and obj3.date_assign and end_date > start_date: + if now < end_date: + diff1 = relativedelta(end_date, start_date) + if diff1.days == 0: + total_hr = int(diff1.minutes) + else: + total_hr = int(diff1.days) * 24 * 60 + int(diff1.minutes) + diff2 = relativedelta(now, start_date) + if diff2.days == 0: + current_hr = int(diff2.minutes) + else: + current_hr = int(diff2.days) * 24 * 60 + int(diff2.minutes) + if total_hr != 0: + obj3.progress = ((current_hr * 100) / total_hr) + else: + obj3.progress = 100 + else: + obj3.progress = 100 + else: + obj3.progress = 0 + + + @api.model + def _track_subtype(self, init_values): + record = self.browse() + if 'kanban_state' in init_values and record.kanban_state == 'blocked': + return 'fleet_car_workshop.mt_task_blocked' + elif 'kanban_state' in init_values and record.kanban_state == 'done': + return 'fleet_car_workshop.mt_task_ready' + elif 'user_id' in init_values and record.user_id: # assigned -> new + return 'fleet_car_workshop.mt_task_new' + elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1: # start stage -> new + return 'fleet_car_workshop.mt_task_new' + elif 'stage_id' in init_values: + return 'fleet_car_workshop.mt_task_stage' + return super(CarWorkshop, self)._track_subtype(init_values) + + @api.model + def create(self, vals): + # context: no_log, because subtype already handle this + context = dict(self.env.context, mail_create_nolog=True) + + # for default stage + if vals.get('vehicle_id') and not context.get('default_vehicle_id'): + context['default_vehicle_id'] = vals.get('vehicle_id') + # user_id change: update date_assign + if vals.get('user_id'): + vals['date_assign'] = fields.Datetime.now() + task = super(CarWorkshop, self.with_context(context)).create(vals) + return task + + @api.multi + def write(self, vals): + now = fields.Datetime.now() + # stage change: update date_last_stage_update + if 'stage_id' in vals: + vals['date_last_stage_update'] = now + # reset kanban state when changing stage + if 'kanban_state' not in vals: + vals['kanban_state'] = 'normal' + # user_id change: update date_assign + if vals.get('user_id'): + vals['date_assign'] = now + + result = super(CarWorkshop, self).write(vals) + + return result + + def _read_group_stages(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): + if context is None: + context = {} + stage_obj = self.pool.get('worksheet.stages') + order = stage_obj._order + access_rights_uid = access_rights_uid or uid + if read_group_order == 'stage_id desc': + order = '%s desc' % order + if 'default_vehicle_id' in context: + search_domain = ['|', ('vehicle_ids', '=', context['default_vehicle_id']), ('id', 'in', ids)] + else: + search_domain = [('id', 'in', ids)] + stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context) + result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) + result.sort(lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) + + fold = {} + for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context): + fold[stage.id] = stage.fold or False + return result, fold + + _group_by_full = { + 'stage_id': _read_group_stages, + } + + @api.cr_uid_ids_context + def onchange_vehicle(self, cr, uid, id, vehicle_id, context=None): + values = {} + if vehicle_id: + vehicle = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context) + if vehicle.exists(): + values['partner_id'] = vehicle.partner_id.id + values['stage_id'] = self.find_stage(cr, uid, [], vehicle_id, [('fold', '=', False)], context=context) + else: + values['stage_id'] = False + return {'value': values} + + def _get_default_vehicle(self, cr, uid, context=None): + if context is None: + context = {} + if 'default_vehicle_id' in context: + vehicle = self.pool.get('car.car').browse(cr, uid, context['default_vehicle_id'], context=context) + if vehicle and vehicle.partner_id: + return vehicle.partner_id.id + return False + + def find_stage(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Override of the base.stage method + Parameter of the stage search taken from the lead: + - section_id: if set, stages must belong to this section or + be a default stage; if not set, stages must be default + stages + """ + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + section_ids = [] + if section_id: + section_ids.append(section_id) + for task in cases: + if task.vehicle_id: + section_ids.append(task.vehicle_id.id) + search_domain = [] + if section_ids: + search_domain = [('|')] * (len(section_ids) - 1) + for section_id in section_ids: + search_domain.append(('vehicle_ids', '=', section_id)) + search_domain += list(domain) + stage_ids = self.pool.get('worksheet.stages').search(cr, uid, search_domain, order=order, context=context) + if stage_ids: + return stage_ids[0] + return False diff --git a/fleet_car_workshop/models/config.py b/fleet_car_workshop/models/config.py new file mode 100644 index 000000000..f82709a90 --- /dev/null +++ b/fleet_car_workshop/models/config.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Nilmar Shereef() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## +from odoo.tools.translate import _ +from odoo import fields, models, api + + +class WorkshopSetting(models.Model): + _name = "workshop.config.setting" + + invoice_journal_type = fields.Many2one('account.journal', string="Car Workshop Journal") + + @api.multi + def execute(self): + return self.env['ir.values'].sudo().set_default( + 'workshop.config.setting', 'invoice_journal_type', self.invoice_journal_type.id) + + def cancel(self, cr, uid, ids, context=None): + act_window = self.pool['ir.actions.act_window'] + action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)]) + if action_ids: + return act_window.read(cr, uid, action_ids[0], [], context=context) + return {} + + +class WorksheetTags(models.Model): + _name = "worksheet.tags" + _description = "Tags of vehicles's tasks, issues..." + + name = fields.Char('Name', required=True) + color = fields.Integer('Color Index') + + _sql_constraints = [ + ('name_uniq', 'unique (name)', "Tag name already exists !"), + ] + + +class WorksheetStages(models.Model): + _name = 'worksheet.stages' + _description = 'worksheet Stage' + _order = 'sequence' + + name = fields.Char(string='Stage Name', required=True) + description = fields.Text(string='Description', translate=True) + sequence = fields.Integer(string='Sequence') + vehicle_ids = fields.Many2many('car.car', 'worksheet_type_rel', 'type_id', 'vehicle_id', string='Vechicles') + fold = fields.Boolean('Folded in Tasks Pipeline', + help='This stage is folded in the kanban view when ' + 'there are no records in that stage to display.') + + def _get_default_vehicle_ids(self, cr, uid, ctx=None): + if ctx is None: + ctx = {} + default_vehicle_id = ctx.get('default_vehicle_id') + return [default_vehicle_id] if default_vehicle_id else None + + _defaults = { + 'sequence': 1, + 'vehicle_ids': _get_default_vehicle_ids, + } + _order = 'sequence' + + +class Services(models.Model): + _inherit = 'product.template' + + type = fields.Selection([('consu', _('Consumable')), ('service', _('Service')), ('product', _('Stockable Product'))], 'Product Type', required=True, + help="A consumable is a product for which you don't manage stock," + " a service is a non-material product provided by a company or an individual.") + + _defaults = { + 'type': 'service', + + } diff --git a/fleet_car_workshop/models/dashboard.py b/fleet_car_workshop/models/dashboard.py new file mode 100644 index 000000000..3d6b20a02 --- /dev/null +++ b/fleet_car_workshop/models/dashboard.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Nilmar Shereef() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## +from odoo.osv import osv +from odoo.tools.translate import _ +from odoo import fields, api + + +class CarVehicle(osv.osv): + _name = 'car.car' + _description = "Vechicles" + _inherit = ['mail.thread'] + + def _get_visibility_selection_id(self, cr, uid, context=None): + return [('portal', _('Customer Works: visible in portal if the customer is a follower')), + ('employees', _('All Employees Work: all employees can access')), + ('followers', _('Private Work: followers only'))] + + _visibility_selections = lambda self, *args, **kwargs: self._get_visibility_selection_id(*args, **kwargs) + + def _compute_attached_docs_count(self): + Attachment = self.env['ir.attachment'] + for vehicle in self: + vehicle.doc_count = Attachment.search_count([ + '|', + '&', + ('res_model', '=', 'car.car'), ('res_id', '=', vehicle.id), + '&', + ('res_model', '=', 'car.worksheet'), ('res_id', 'in', vehicle.task_ids.ids) + ]) + + def _compute_task_count(self): + for vehicle in self: + vehicle.task_count = len(vehicle.task_ids) + + def attachment_tree_views(self): + self.ensure_one() + domain = [ + '|', + '&', ('res_model', '=', 'car.car'), ('res_id', 'in', self.ids), + '&', ('res_model', '=', 'car.workshop'), ('res_id', 'in', self.task_ids.ids)] + + return { + 'name': _('Attachments'), + 'domain': domain, + 'res_model': 'ir.attachment', + 'type': 'ir.actions.act_window', + 'view_id': False, + 'view_mode': 'kanban,tree,form', + 'view_type': 'form', + 'help': _('''

+ Documents are attached to the tasks and issues of your Worksheet.

+ Send messages or log internal notes with attachments to link + documents to your Worksheet. +

'''), + 'limit': 80, + 'context': "{'default_res_model': '%s','default_res_id': %d}" % (self._name, self.id) + } + + active = fields.Boolean('Active',default=True) + name = fields.Many2one('fleet.vehicle', string='Vehicle Name', track_visibility='onchange', required=True) + sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of Projects.") + + label_tasks = fields.Char(string='Use Tasks as', help="Gives label to Work on kanban view.", default="Task") + worksheet = fields.One2many('car.workshop', 'vehicle_id', string="Task Activities") + + type_ids = fields.Many2many('worksheet.stages', 'car_workshop_type_rel', + 'vehicle_id', 'type_id', string='Worksheet Stages', + states={'close': [('readonly', True)], 'cancelled': [('readonly', True)]}) + task_count = fields.Integer(compute=_compute_task_count, type='integer', string="Tasks", ) + task_ids = fields.One2many('car.workshop', 'vehicle_id', + domain=['|', ('stage_id.fold', '=', False), ('stage_id', '=', False)]) + doc_count = fields.Integer(compute=_compute_attached_docs_count, string="Number of documents attached") + color = fields.Integer(string='Color Index') + partner_id = fields.Many2one('res.partner', string='Customer') + state = fields.Selection([('draft', 'New'), + ('open', 'In Progress'), + ('cancelled', 'Cancelled'), + ('pending', 'Pending'), + ('close', 'Closed')], string='Status', required=True, + track_visibility='onchange',default='open', copy=False) + + date_start = fields.Date(string='Start Date') + date = fields.Date(string='Expiration Date', select=True, track_visibility='onchange'), + use_tasks = fields.Boolean(string='Tasks', default=True) + image_medium = fields.Binary(string="Logo (medium)") + + @api.onchange('name') + def on_change_vehicle(self): + self.image_medium = self.name.image_medium diff --git a/fleet_car_workshop/models/timesheet.py b/fleet_car_workshop/models/timesheet.py new file mode 100644 index 000000000..19c26e2b9 --- /dev/null +++ b/fleet_car_workshop/models/timesheet.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Nilmar Shereef() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +from odoo import fields, models, api + + +class PlannedWork (models.Model): + _name = 'planned.work' + + planned_work = fields.Many2one('product.template', string='Planned work', domain=[('type', '=', 'service')]) + time_spent = fields.Float(string='Estimated Time') + work_date = fields.Datetime(string='Date') # Date of work planned:planned date + responsible = fields.Many2one('res.users', string='Responsible') + work_id = fields.Many2one('car.workshop', string="Work id") + work_cost = fields.Float(string="Service Cost") + completed = fields.Boolean(string="Completed") + duration = fields.Float(string='Duration') + work_date2 = fields.Datetime(string='Date') # Date of work completed/done:completed date + + @api.onchange('planned_work') + def get_price(self): + self.work_cost = self.planned_work.lst_price + + +class MaterialUsed (models.Model): + _name = 'material.used' + + material = fields.Many2one('product.template', string='Products') + amount = fields.Integer(string='Quantity') + price = fields.Float(string='Unit Price') + material_id = fields.Many2one('car.workshop') + _defaults = { + 'amount': 1, } + + @api.onchange('material') + def get_price(self): + self.price = self.material.lst_price diff --git a/fleet_car_workshop/security/ir.model.access.csv b/fleet_car_workshop/security/ir.model.access.csv new file mode 100644 index 000000000..5f3868aa3 --- /dev/null +++ b/fleet_car_workshop/security/ir.model.access.csv @@ -0,0 +1,22 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + + + + + + +access_car_car_user,car.car.user,fleet_car_workshop.model_car_car,fleet.fleet_group_user,1,0,0,0 +access_car_workshop_user,car.workshop.user,fleet_car_workshop.model_car_workshop,fleet.fleet_group_user,1,0,0,0 +access_worksheet_tags_all_user,car.car_tags_all,fleet_car_workshop.model_worksheet_tags,fleet.fleet_group_user,1,0,0,0 +access_worksheet_stages_user,worksheet.stages.user,fleet_car_workshop.model_worksheet_stages,fleet.fleet_group_user,1,0,0,0 +access_service_product_user,car.car.products.user,fleet_car_workshop.model_product_template,fleet.fleet_group_user,0,0,0,0 +access_material_used_user,material.used.user,fleet_car_workshop.model_material_used,fleet.fleet_group_user,1,0,0,0 +access_planned_work_user,planned.work.user,fleet_car_workshop.model_planned_work,fleet.fleet_group_user,1,0,0,0 + +access_car_car_manager,car.car.manager,fleet_car_workshop.model_car_car,fleet.fleet_group_manager,1,1,1,1 +access_car_workshop_manager,car.workshop.manager,fleet_car_workshop.model_car_workshop,fleet.fleet_group_manager,1,1,1,1 +access_worksheet_tags_all_manager,car.car_tags_all.manager,fleet_car_workshop.model_worksheet_tags,fleet.fleet_group_manager,1,1,1,1 +access_worksheet_stages_manager,worksheet.stages.manager,fleet_car_workshop.model_worksheet_stages,fleet.fleet_group_manager,1,1,1,1 +access_service_product_manager,car.car.products.manager,fleet_car_workshop.model_product_template,fleet.fleet_group_manager,1,1,1,1 +access_material_used_manager,material.used.manager,fleet_car_workshop.model_material_used,fleet.fleet_group_manager,1,1,1,1 +access_planned_work_manager,planned.work.manager,fleet_car_workshop.model_planned_work,fleet.fleet_group_manager,1,1,1,1 \ No newline at end of file diff --git a/fleet_car_workshop/security/workshop_security.xml b/fleet_car_workshop/security/workshop_security.xml new file mode 100644 index 000000000..961bc0884 --- /dev/null +++ b/fleet_car_workshop/security/workshop_security.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + Manager has all rights on vehicle + + + + + diff --git a/fleet_car_workshop/static/description/1.png b/fleet_car_workshop/static/description/1.png new file mode 100644 index 000000000..37fcbd1f8 Binary files /dev/null and b/fleet_car_workshop/static/description/1.png differ diff --git a/fleet_car_workshop/static/description/10.png b/fleet_car_workshop/static/description/10.png new file mode 100644 index 000000000..120f03120 Binary files /dev/null and b/fleet_car_workshop/static/description/10.png differ diff --git a/fleet_car_workshop/static/description/2.png b/fleet_car_workshop/static/description/2.png new file mode 100644 index 000000000..fe6cc7d47 Binary files /dev/null and b/fleet_car_workshop/static/description/2.png differ diff --git a/fleet_car_workshop/static/description/4.png b/fleet_car_workshop/static/description/4.png new file mode 100644 index 000000000..2c85d2295 Binary files /dev/null and b/fleet_car_workshop/static/description/4.png differ diff --git a/fleet_car_workshop/static/description/5.png b/fleet_car_workshop/static/description/5.png new file mode 100644 index 000000000..33db91bc4 Binary files /dev/null and b/fleet_car_workshop/static/description/5.png differ diff --git a/fleet_car_workshop/static/description/7.png b/fleet_car_workshop/static/description/7.png new file mode 100644 index 000000000..119d302bd Binary files /dev/null and b/fleet_car_workshop/static/description/7.png differ diff --git a/fleet_car_workshop/static/description/8.png b/fleet_car_workshop/static/description/8.png new file mode 100644 index 000000000..9b7656ffb Binary files /dev/null and b/fleet_car_workshop/static/description/8.png differ diff --git a/fleet_car_workshop/static/description/9.png b/fleet_car_workshop/static/description/9.png new file mode 100644 index 000000000..a7b6f8b38 Binary files /dev/null and b/fleet_car_workshop/static/description/9.png differ diff --git a/fleet_car_workshop/static/description/banner.jpg b/fleet_car_workshop/static/description/banner.jpg new file mode 100644 index 000000000..e1a0fbdb1 Binary files /dev/null and b/fleet_car_workshop/static/description/banner.jpg differ diff --git a/fleet_car_workshop/static/description/cybro_logo.png b/fleet_car_workshop/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/fleet_car_workshop/static/description/cybro_logo.png differ diff --git a/fleet_car_workshop/static/description/icon.png b/fleet_car_workshop/static/description/icon.png new file mode 100644 index 000000000..a17017510 Binary files /dev/null and b/fleet_car_workshop/static/description/icon.png differ diff --git a/fleet_car_workshop/static/description/index.html b/fleet_car_workshop/static/description/index.html new file mode 100644 index 000000000..e918e4631 --- /dev/null +++ b/fleet_car_workshop/static/description/index.html @@ -0,0 +1,179 @@ +
+
+

Car Workshop Management

+

+

Cybrosys Technologies

+ + +

+Car Workshop Management is Cybrosys Generic Module to manage automobile workshop with +great ease. Keep track of everything, like vehicle owner details, + Works assigned, Bill details of service provided,etc..
Some other features are as below: +

+
    +
  •    User Friendly Interface.
  • +
  •    Effective Time management.
  • +
  •    Separate Journal Configuration..
  • +
  •    Integrated with Accounting.
  • +
  •    High Scalability.
  • +
+
+
+ +
+
+
+

Dashboard/All Vehicles

+

Whole Workshop Analysis

+
+
+
+ +
+
+
+
+ +
+
+

All works

+
+

+

Goto Car Workshop -->Search -->Worksheet

+

Organise all the works according to their Status. Works/Tasks assigned works using the kanban view and + control deadlines in the calendar view.Each Work may have it's own stages. +

+
+
+

Kanban

+ + +
+
+

Calendar

+ +
+
+
+ +
+
+
+

Worksheet View

+

All details related to a work/task

+
+
+
+ +
+
+
+
+ +
+
+

Timesheet

+
+

+

Goto Car Workshop >Search >Worksheet >Timesheet

+

+

* In timesheet ,Planned work is the sub works related to main Work. By ticking + 'Completed' that work will be automatically updated to Work done.

+

* Work done is the details of completed works.

+

* Hour spent is time taken for completed work(Work Done).

+

* Remaining Hour is total time left(Difference between Total Time and Hour Spent).

+

+
+
+
+ +
+
+
+ +
+
+
+

Report

+

Tabular Details of Vehicles

+
+
+
+ +
+
+
+
+ +
+
+
+

Settings

+

Configure Preferred journal for Invoicing

+
+
+

+

Goto Car Workshop >Configuration >Settings

+

+
+
+ +
+
+
+
+ +
+
+
+

Separate Journal

+

Invoices from the car workshop will be saved to Separate journal.

+
+
+

+

Goto Car Workshop >Search >Worksheet >Create Invoices

+

+
+
+ +
+
+
+
+ +
+
+

You Looking for a free Documentation of this Application.?

+

Give a Request Mail to:    odoo@cybrosys.com

+
+
+
+ +
+

Need Any Help?

+ +
+ + + diff --git a/fleet_car_workshop/static/src/css/vehicles.css b/fleet_car_workshop/static/src/css/vehicles.css new file mode 100644 index 000000000..c6e6d31c5 --- /dev/null +++ b/fleet_car_workshop/static/src/css/vehicles.css @@ -0,0 +1,40 @@ + +.oe_kanban_project_avatars img { + width: 30px; + border-radius: 2px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} +.oe_form_gantt_avatars:after { + font-family: "mnmliconsRegular" !important; + font-size: 21px; + font-weight: 300 !important; + content: "y"; + top: 3px; + position: relative; +} + +.openerp .oe_kanban_view .oe_kanban_project { + width: 250px; + min-height: 160px !important; + cursor: default; +} + +.openerp .oe_percent strong:after { + content: "%"; +} + +.openerp .oe_margin_top_8 { + margin-top: 8px; +} +.openerp .oe_kanban_project .oe_kanban_project_list .col-md-6 a{ + margin-left: 5px; +} + +/* Kanban status as label in project stage form view */ +.openerp label.oe_project_kanban_legend { + min-width: inherit !important; + margin-top: 6px; + margin-right: 8px; +} diff --git a/fleet_car_workshop/static/src/less/car_dashboard.less b/fleet_car_workshop/static/src/less/car_dashboard.less new file mode 100644 index 000000000..36c48bb9c --- /dev/null +++ b/fleet_car_workshop/static/src/less/car_dashboard.less @@ -0,0 +1,98 @@ +.o_kanban_view.o_kanban_dashboard.o_project_kanban { + + .o_kanban_record { + .o-flex-display(); + position: relative; + } + + .o_project_kanban_main { + .o-flex(0, 0, auto); + padding: @odoo-horizontal-padding; + width: 70%; // for IE11 overflow issue + + // give space for manage section at the bottom + margin-bottom: @odoo-horizontal-padding/2 + 19px; + + .o_kanban_card_content { + font-size: 12px; + .o_primary { + font-size: larger; + } + } + + .o_project_kanban_manage { + .o-position-absolute(@bottom: 0, @left: 0); + padding: @odoo-horizontal-padding/2 @odoo-horizontal-padding; + } + .o_kanban_card_manage_pane .o_kanban_card_manage_title { + margin: 0; + } + } + + .o_project_kanban_boxes { + width: 30%; + .o-flex(0, 0, auto); + + .o-flex-display(); + .o-flex-flow(column, nowrap); + + > a:hover { + text-decoration: none; + } + + .o_value { + font-size: x-large; + color: white; + display: block; + } + .o_label { + color: white; + } + .o_needaction{ + position: absolute; + top: 5px; + right: 5px; + color: white; + font-size: small; + opacity: 0.5; + &:hover{ + opacity:1; + } + &::before { + content: "\f086"; + font: normal normal normal 14px/1 FontAwesome; + } + } + + .o_project_kanban_box { + position: relative; + text-align: center; + padding: 15px 0 15px 0; + .o-flex(1, 1, auto); + .o-flex-display(); + .o-align-items(center); + .o-flex-flow(column, nowrap); + .o-justify-content(center); + } + .o_project_kanban_box:nth-child(even) { + background-color: grey; + } + .o_project_kanban_box:nth-child(odd) { + background-color: @odoo-brand-optional; + } + } +} + +.o_kanban_task_cover_image { + .o-columns(200px, 4); + > img { + cursor: pointer; + margin-bottom: 1em; + border: 1px solid transparent; + + &.o_selected { + border-color: @odoo-brand-secondary; + box-shadow: 0px 0px 2px 2px @odoo-brand-secondary; + } + } +} diff --git a/fleet_car_workshop/views/car_dashboard.xml b/fleet_car_workshop/views/car_dashboard.xml new file mode 100644 index 000000000..f277c56dc --- /dev/null +++ b/fleet_car_workshop/views/car_dashboard.xml @@ -0,0 +1,189 @@ + + + car.car.form + car.car + +
+
+ +
+ +
+ + + + + +
+ +
+

+ +

+
+
+ +
+
+
+ + + + + + + + +
+
+ + +
+
+
+
+ + + car.car.select + car.car + + + + + + + + + + + + + + + + + + car.car.kanban + car.car + + + + + + + + + + + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ Settings +
+ +
+
+
    +
+
+
+ More +
+
+ + +
+
+
+
+
+
+ + + car.car.tree + car.car + child_ids + + + + + + + + + + + Vehicles + car.car + form + [] + kanban,form + + {'search_default_Current': 1} + +

+ Create a new project. +

+

+ Organize your activities (plan tasks, track issues, invoice timesheets) for internal, personal or customer projects. +

+
+
+ + + Vehicles + car.car + form + [] + tree,form + + {'search_default_Current': 1} + +

+ Create a new vehicle. +

+
+
+ + + +
\ No newline at end of file diff --git a/fleet_car_workshop/views/config_setting.xml b/fleet_car_workshop/views/config_setting.xml new file mode 100644 index 000000000..84652438e --- /dev/null +++ b/fleet_car_workshop/views/config_setting.xml @@ -0,0 +1,32 @@ + + + workshop settings + workshop.config.setting + +
+
+
+ +
+ + +
+
+
+
+ + + Settings + ir.actions.act_window + workshop.config.setting + + form + inline + + + + +
\ No newline at end of file diff --git a/fleet_car_workshop/views/report.xml b/fleet_car_workshop/views/report.xml new file mode 100644 index 000000000..06bbdb98a --- /dev/null +++ b/fleet_car_workshop/views/report.xml @@ -0,0 +1,15 @@ + + + Worksheets + car.workshop + pivot,graph + +

+ Odoo's car workshop management allows you to manage the pipeline of your work efficiently. You can track progress, discuss on works, attach documents, etc. +

+
+
+ + + +
\ No newline at end of file diff --git a/fleet_car_workshop/views/timesheet_view.xml b/fleet_car_workshop/views/timesheet_view.xml new file mode 100644 index 000000000..9732a2298 --- /dev/null +++ b/fleet_car_workshop/views/timesheet_view.xml @@ -0,0 +1,53 @@ + + + + planned.work.form + planned.work + +
+ + + + + + + + + + + +
+
+
+ + + material.used.form + material.used + +
+ + + + + + + + + +
+
+
+ + + material.used.tree + material.used + + + + + + + + +
+
\ No newline at end of file diff --git a/fleet_car_workshop/views/vehicle.xml b/fleet_car_workshop/views/vehicle.xml new file mode 100644 index 000000000..f5aff98c8 --- /dev/null +++ b/fleet_car_workshop/views/vehicle.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/fleet_car_workshop/views/worksheet_stages.xml b/fleet_car_workshop/views/worksheet_stages.xml new file mode 100644 index 000000000..b3ed90bb5 --- /dev/null +++ b/fleet_car_workshop/views/worksheet_stages.xml @@ -0,0 +1,90 @@ + + + + worksheet.stages.form + worksheet.stages + +
+ + + + + + + + + + +

+ You can also add a description to help your co-workers understand the meaning and purpose of the stage. +

+ +
+ + + +
+
+
+ + + worksheet.stages.tree + worksheet.stages + + + + + + + + + + + + Stages + worksheet.stages + form + + + + Tags + worksheet.tags + +
+ + + +
+
+
+ + + Tags + worksheet.tags + form + +

+ Click to add a new tag. +

+
+
+ + + Service-Products + ir.actions.act_window + product.template + kanban,tree,form + form + {"search_default_services":'service'} + +

+ Click to define a new product. +

+
+
+ + + + +
+
diff --git a/fleet_car_workshop/views/worksheet_views.xml b/fleet_car_workshop/views/worksheet_views.xml new file mode 100644 index 000000000..47090c72a --- /dev/null +++ b/fleet_car_workshop/views/worksheet_views.xml @@ -0,0 +1,285 @@ + + + + Demo scheduler + + 1 + minutes + -1 + + + + + + + worksheet.form.view + car.workshop + +
+
+
+ +
+ + + +
+

+ + +

+
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + car.workshop.kanban + car.workshop + + + + + + + + + + + + + + + +
+ + +
+
+ +
+
+ +
+
+ + oe_kanban_text_red + +
+
+ + +
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ + + + worksheet.tree.view + car.workshop + + + + + + + + + + + + + + worksheet.calender.view + car.workshop + + + + + + + + + + + car.workshop.pivot + car.workshop + + + + + + + + + + car.workshop.graph + car.workshop + + + + + + + + + + Worksheets + car.workshop + {'search_default_vehicle_id': active_id} + kanban,tree,form,calendar,pivot,graph + +

+ Odoo's car workshop management allows you to manage the pipeline of your work efficiently. You can track progress, discuss on works, attach documents, etc. +

+
+
+ + + car.workshop.search.form + car.workshop + + + + + + + + + + + + + + + + + + + + + + + + + + + + Worksheets + car.workshop + kanban,tree,form,calendar,pivot,graph + +

+ Odoo's car workshop management allows you to manage the pipeline of your work efficiently. You can track progress, discuss on works, attach documents, etc. +

+
+
+ + + + + + + \ No newline at end of file diff --git a/fleet_car_workshop/views/workshop_data.xml b/fleet_car_workshop/views/workshop_data.xml new file mode 100644 index 000000000..248025506 --- /dev/null +++ b/fleet_car_workshop/views/workshop_data.xml @@ -0,0 +1,77 @@ + + + + + + Car + car.car + + + + Car Worksheet + car.workshop + + + + + + + Task Opened + car.workshop + + + Task opened + + + Task Blocked + car.workshop + + Task blocked + + + Task Ready + car.workshop + + Task ready for Next Stage + + + Stage Changed + car.workshop + + Stage changed + + + + Task Opened + 10 + car.car + + + vehicle_id + + + Task Blocked + 11 + car.car + + + vehicle_id + + + Task Ready + 12 + car.car + + + vehicle_id + + + Task Stage Changed + 13 + car.car + + + vehicle_id + + + \ No newline at end of file