Tower: upload tk_construction_management 18.0.2.0.8 (was 18.0.2.0.8, via marketplace)
This commit is contained in:
23
addons/tk_construction_management/models/__init__.py
Normal file
23
addons/tk_construction_management/models/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from . import construction_site
|
||||
from . import res_partner
|
||||
from . import configuration
|
||||
from . import construction_project
|
||||
from . import department
|
||||
from . import con_purchase_stock
|
||||
from . import job_costing
|
||||
from . import job_order
|
||||
from . import material_requisition
|
||||
from . import con_template
|
||||
from . import internal_transfer
|
||||
from . import project_task
|
||||
from . import construction_scrap
|
||||
from . import construction_user
|
||||
from . import construction_dashboard
|
||||
from . import construction_emplyoee
|
||||
from . import res_config
|
||||
from . import sub_contract
|
||||
from . import rate_analysis
|
||||
from . import budget
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
230
addons/tk_construction_management/models/budget.py
Normal file
230
addons/tk_construction_management/models/budget.py
Normal file
@@ -0,0 +1,230 @@
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class SubProjectBudget(models.Model):
|
||||
_name = 'sub.project.budget'
|
||||
_description = "Sub Project Budget"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string="Title")
|
||||
progress = fields.Float(string="Budget Utilization(%)", compute="compute_used_budget")
|
||||
total_budget_amount = fields.Monetary(string="Total Budget Amount", compute="compute_used_budget")
|
||||
utilization_amount = fields.Monetary(string="Budget Utilization", compute="compute_used_budget")
|
||||
start_date = fields.Date(string="Start Date")
|
||||
end_date = fields.Date(string="End Date")
|
||||
site_id = fields.Many2one('tk.construction.site', string="Project")
|
||||
sub_project_id = fields.Many2one('tk.construction.project', string="Sub Project")
|
||||
responsible_id = fields.Many2one('res.users', default=lambda self: self.env.user and self.env.user.id or False,
|
||||
string="Responsible")
|
||||
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
status = fields.Selection([('draft', 'Draft'), ('waiting_approval', 'Waiting Approval'), ('approved', 'Approved'),
|
||||
('in_progress', 'In Progress'), ('complete', 'Complete'), ('cancel', 'Cancel'),
|
||||
('reject', 'Reject')], default='draft')
|
||||
budget_count = fields.Integer(string="Budget Line Count", compute="compute_count")
|
||||
reject_reason = fields.Text(string="Reject Reason")
|
||||
budget_line_ids = fields.One2many('project.budget', 'sub_project_budget_id')
|
||||
|
||||
def compute_count(self):
|
||||
for rec in self:
|
||||
rec.budget_count = self.env['project.budget'].search_count([('sub_project_budget_id', '=', rec.id)])
|
||||
|
||||
def action_view_budget_line(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Budget'),
|
||||
'res_model': 'project.budget',
|
||||
'domain': [('sub_project_budget_id', '=', self.id)],
|
||||
'context': {'default_sub_project_budget_id': self.id},
|
||||
'view_mode': 'list,kanban,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_department_approval(self):
|
||||
self.status = 'waiting_approval'
|
||||
|
||||
def action_approve_budget(self):
|
||||
self.status = 'approved'
|
||||
|
||||
def action_reject_budget(self):
|
||||
self.status = 'reject'
|
||||
|
||||
def action_complete_budget(self):
|
||||
self.status = 'complete'
|
||||
|
||||
def action_cancel_budget(self):
|
||||
self.status = 'cancel'
|
||||
|
||||
def action_reset_draft_budget(self):
|
||||
self.status = 'draft'
|
||||
|
||||
@api.depends('budget_line_ids', 'budget_line_ids.budget', 'budget_line_ids.remaining_budget')
|
||||
def compute_used_budget(self):
|
||||
for rec in self:
|
||||
utilize_budget = 0.0
|
||||
total_budget_amount = 0.0
|
||||
remaining_budget_amount = 0.0
|
||||
for data in rec.budget_line_ids:
|
||||
total_budget_amount = total_budget_amount + data.budget
|
||||
remaining_budget_amount = remaining_budget_amount + data.remaining_budget
|
||||
utilize_budget = 100 - (
|
||||
(remaining_budget_amount * 100 / total_budget_amount) if total_budget_amount > 0 else 100)
|
||||
rec.progress = round(utilize_budget, 1)
|
||||
rec.total_budget_amount = total_budget_amount
|
||||
rec.utilization_amount = total_budget_amount - remaining_budget_amount
|
||||
|
||||
|
||||
class ProjectBudget(models.Model):
|
||||
_name = 'project.budget'
|
||||
_description = "Project Budget"
|
||||
_rec_name = 'job_type_id'
|
||||
|
||||
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
sub_project_budget_id = fields.Many2one('sub.project.budget', string="Sub Project Budget")
|
||||
project_id = fields.Many2one(related="sub_project_budget_id.sub_project_id", string="Project")
|
||||
site_id = fields.Many2one(related="project_id.construction_site_id", string="Site")
|
||||
job_type_id = fields.Many2one('job.type', string="Work Type")
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
boq_qty = fields.Float(string="BOQ Qty")
|
||||
additional_qty = fields.Float(string="Add. Qty", help="Additional Qty")
|
||||
total_qty = fields.Float(string="Total Qty.", compute="compute_total_qty")
|
||||
rate_analysis_id = fields.Many2one('rate.analysis', string="Rate Analysis")
|
||||
price_per_qty = fields.Monetary(string="Price / Qty", compute="compute_rate_analysis_info", store=True)
|
||||
untaxed_amount = fields.Monetary(string="Untaxed Amount", compute="compute_rate_analysis_info", store=True)
|
||||
tax_amount = fields.Monetary(string="Tax Amount", compute="compute_rate_analysis_info", store=True)
|
||||
budget = fields.Monetary(string="Total Budget Amount", compute="compute_rate_analysis_info", store=True)
|
||||
# Spent
|
||||
material_spent = fields.Monetary(string="Material Spent", compute="_compute_budget_calculation")
|
||||
equipment_spent = fields.Monetary(string="Equipment Spent", compute="_compute_budget_calculation")
|
||||
labour_spent = fields.Monetary(string="Labour Spent", compute="_compute_budget_calculation")
|
||||
overhead_spent = fields.Monetary(string="Overhead Spent", compute="_compute_budget_calculation")
|
||||
remaining_budget = fields.Monetary(string="Remaining Budget", compute="_compute_budget_calculation")
|
||||
|
||||
# Spent in Percentage
|
||||
total_spent = fields.Float(string="Utilization(%)", compute="_compute_budget_calculation")
|
||||
boq_used_qty = fields.Float(string="Used Qty", compute="_compute_budget_calculation")
|
||||
|
||||
@api.depends('boq_qty', 'additional_qty')
|
||||
def compute_total_qty(self):
|
||||
for rec in self:
|
||||
rec.total_qty = rec.boq_qty + rec.additional_qty
|
||||
|
||||
@api.depends('rate_analysis_id', 'rate_analysis_id.total_amount', 'rate_analysis_id.untaxed_amount',
|
||||
'rate_analysis_id.tax_amount', 'total_qty')
|
||||
def compute_rate_analysis_info(self):
|
||||
for rec in self:
|
||||
untaxed_amount = 0.0
|
||||
tax_amount = 0.0
|
||||
price_per_qty = 0.0
|
||||
budget = 0.0
|
||||
if rec.rate_analysis_id:
|
||||
price_per_qty = rec.rate_analysis_id.total_amount
|
||||
untaxed_amount = rec.rate_analysis_id.untaxed_amount
|
||||
tax_amount = rec.rate_analysis_id.tax_amount
|
||||
budget = rec.total_qty * price_per_qty
|
||||
rec.untaxed_amount = untaxed_amount
|
||||
rec.tax_amount = tax_amount
|
||||
rec.price_per_qty = price_per_qty
|
||||
rec.budget = budget
|
||||
|
||||
@api.depends('project_id', 'sub_category_id', 'job_type_id', 'budget', 'total_qty')
|
||||
def _compute_budget_calculation(self):
|
||||
for rec in self:
|
||||
budget_phase_ids = self.env['job.costing'].search(
|
||||
[('project_id', '=', rec.project_id.id), ('activity_id', '=', rec.job_type_id.id)]).mapped('id')
|
||||
domain = [('project_id', '=', rec.project_id.id), ('work_type_id', '=', rec.job_type_id.id),
|
||||
('sub_category_id', '=', rec.sub_category_id.id), ('state', '=', 'complete'),
|
||||
('job_sheet_id', 'in', budget_phase_ids)]
|
||||
material_spent = sum(self.env['order.material.line'].search(domain).mapped('total_price'))
|
||||
equipment_spent = sum(self.env['order.equipment.line'].search(domain).mapped('total_cost'))
|
||||
labour_spent = sum(self.env['order.labour.line'].search(domain).mapped('sub_total'))
|
||||
overhead_spent = sum(self.env['order.overhead.line'].search(domain).mapped('sub_total'))
|
||||
remaining_budget = rec.budget - (material_spent + equipment_spent + labour_spent + overhead_spent)
|
||||
total_spent = 100 - ((remaining_budget * 100 / rec.budget) if rec.budget > 0 else 100)
|
||||
boq_used_qty = rec.total_qty - (
|
||||
(remaining_budget * rec.total_qty / rec.budget) if rec.budget > 0 else rec.total_qty)
|
||||
rec.equipment_spent = equipment_spent
|
||||
rec.labour_spent = labour_spent
|
||||
rec.overhead_spent = overhead_spent
|
||||
rec.material_spent = material_spent
|
||||
rec.remaining_budget = remaining_budget
|
||||
rec.total_spent = round(total_spent, 1)
|
||||
rec.boq_used_qty = boq_used_qty
|
||||
|
||||
@api.onchange('sub_category_id', 'job_type_id')
|
||||
def _onchange_sub_category(self):
|
||||
ids = []
|
||||
for rec in self:
|
||||
if not rec.job_type_id:
|
||||
return
|
||||
ids = rec.job_type_id.sub_category_ids.ids
|
||||
return {'domain': {'sub_category_id': [('id', 'in', ids)]}}
|
||||
|
||||
def action_view_material_budget(self):
|
||||
budget_phase_ids = self.env['job.costing'].search(
|
||||
[('project_id', '=', self.project_id.id), ('activity_id', '=', self.job_type_id.id)]).mapped('id')
|
||||
domain = [('project_id', '=', self.project_id.id), ('work_type_id', '=', self.job_type_id.id),
|
||||
('sub_category_id', '=', self.sub_category_id.id), ('state', '=', 'complete'),
|
||||
('job_sheet_id', 'in', budget_phase_ids)]
|
||||
ids = self.env['order.material.line'].search(domain).mapped('id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Material'),
|
||||
'res_model': 'order.material.line',
|
||||
'domain': [('id', 'in', ids)],
|
||||
'context': {'create': False},
|
||||
'view_mode': 'list',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_equipment_budget(self):
|
||||
budget_phase_ids = self.env['job.costing'].search(
|
||||
[('project_id', '=', self.project_id.id), ('activity_id', '=', self.job_type_id.id)]).mapped('id')
|
||||
domain = [('project_id', '=', self.project_id.id), ('work_type_id', '=', self.job_type_id.id),
|
||||
('sub_category_id', '=', self.sub_category_id.id), ('state', '=', 'complete'),
|
||||
('job_sheet_id', 'in', budget_phase_ids)]
|
||||
ids = self.env['order.equipment.line'].search(domain).mapped('id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Equipment'),
|
||||
'res_model': 'order.equipment.line',
|
||||
'domain': [('id', 'in', ids)],
|
||||
'context': {'create': False},
|
||||
'view_mode': 'list',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_labour_budget(self):
|
||||
budget_phase_ids = self.env['job.costing'].search(
|
||||
[('project_id', '=', self.project_id.id), ('activity_id', '=', self.job_type_id.id)]).mapped('id')
|
||||
domain = [('project_id', '=', self.project_id.id), ('work_type_id', '=', self.job_type_id.id),
|
||||
('sub_category_id', '=', self.sub_category_id.id), ('state', '=', 'complete'),
|
||||
('job_sheet_id', 'in', budget_phase_ids)]
|
||||
ids = self.env['order.labour.line'].search(domain).mapped('id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Labour'),
|
||||
'res_model': 'order.labour.line',
|
||||
'domain': [('id', 'in', ids)],
|
||||
'context': {'create': False},
|
||||
'view_mode': 'list',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_overhead_budget(self):
|
||||
budget_phase_ids = self.env['job.costing'].search(
|
||||
[('project_id', '=', self.project_id.id), ('activity_id', '=', self.job_type_id.id)]).mapped('id')
|
||||
domain = [('project_id', '=', self.project_id.id), ('work_type_id', '=', self.job_type_id.id),
|
||||
('sub_category_id', '=', self.sub_category_id.id), ('state', '=', 'complete'),
|
||||
('job_sheet_id', 'in', budget_phase_ids)]
|
||||
ids = self.env['order.overhead.line'].search(domain).mapped('id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Overhead'),
|
||||
'res_model': 'order.overhead.line',
|
||||
'domain': [('id', 'in', ids)],
|
||||
'context': {'create': False},
|
||||
'view_mode': 'list',
|
||||
'target': 'current'
|
||||
}
|
||||
131
addons/tk_construction_management/models/con_purchase_stock.py
Normal file
131
addons/tk_construction_management/models/con_purchase_stock.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class ConstructionPurchase(models.Model):
|
||||
_inherit = 'purchase.order'
|
||||
|
||||
material_req_id = fields.Many2one('material.requisition', string="Material Requisition")
|
||||
project_id = fields.Many2one(related="material_req_id.project_id", string="Sub Project", store=True)
|
||||
job_order_id = fields.Many2one('job.order', string="Work order")
|
||||
purchase_order = fields.Selection([('equipment', 'Equipment'), ('labour', 'Labour'), ('overhead', 'Overhead')],
|
||||
string="Source Ref.")
|
||||
equipment_subcontract_id = fields.Many2one('equipment.subcontract', string="Subcontract")
|
||||
labour_subcontract_id = fields.Many2one('labour.subcontract', string="Subcontract ")
|
||||
overhead_subcontract_id = fields.Many2one('overhead.subcontract', string="Subcontract ")
|
||||
|
||||
def _prepare_invoice(self):
|
||||
res = super(ConstructionPurchase, self)._prepare_invoice()
|
||||
if self.material_req_id:
|
||||
res['material_req_id'] = self.material_req_id.id
|
||||
if self.job_order_id:
|
||||
res['job_order_id'] = self.job_order_id.id
|
||||
res['purchase_order'] = self.purchase_order
|
||||
res['equipment_subcontract_id'] = self.equipment_subcontract_id.id
|
||||
res['labour_subcontract_id'] = self.labour_subcontract_id.id
|
||||
res['overhead_subcontract_id'] = self.overhead_subcontract_id.id
|
||||
return res
|
||||
|
||||
|
||||
class ConstructionBills(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
material_req_id = fields.Many2one('material.requisition', string="Material Requisition")
|
||||
project_id = fields.Many2one(related="material_req_id.project_id", string="Sub Project", store=True)
|
||||
job_order_id = fields.Many2one('job.order', string="Work order")
|
||||
purchase_order = fields.Selection([('equipment', 'Equipment'), ('labour', 'Labour'), ('overhead', 'Overhead')],
|
||||
string="Source Ref.")
|
||||
equipment_subcontract_id = fields.Many2one('equipment.subcontract', string="Subcontract")
|
||||
labour_subcontract_id = fields.Many2one('labour.subcontract', string="Subcontract ")
|
||||
overhead_subcontract_id = fields.Many2one('overhead.subcontract', string="Subcontract ")
|
||||
|
||||
|
||||
class ConstructionProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
last_po_price = fields.Monetary(string="Last Purchase Price")
|
||||
is_material = fields.Boolean(string="Is Material")
|
||||
is_equipment = fields.Boolean(string="Is Equipment")
|
||||
is_labour = fields.Boolean(string="Is Labour")
|
||||
is_overhead = fields.Boolean(string="Is Overhead")
|
||||
is_expense = fields.Boolean(string="Is Expense")
|
||||
|
||||
|
||||
class ConstructionWarehouse(models.Model):
|
||||
_inherit = 'stock.warehouse'
|
||||
|
||||
project_id = fields.Many2one('tk.construction.project', string="Project")
|
||||
consume_stock_location_id = fields.Many2one('stock.location', string="Consume Location")
|
||||
|
||||
|
||||
class ConstructionDelivery(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
code = fields.Selection(related='picking_type_id.code', store=True, string="Code ")
|
||||
transfer_id = fields.Many2one('internal.transfer', string="Transfer Ref.")
|
||||
consume_order_id = fields.Many2one('job.order', string="Consume Order Ref.")
|
||||
material_consume_id = fields.Many2one('material.consume', string="Material Consume Ref.")
|
||||
|
||||
def button_validate(self):
|
||||
# Clean-up the context key at validation to avoid forcing the creation of immediate
|
||||
# transfers.
|
||||
ctx = dict(self.env.context)
|
||||
ctx.pop('default_immediate_transfer', None)
|
||||
self = self.with_context(ctx)
|
||||
|
||||
# Sanity checks.
|
||||
if not self.env.context.get('skip_sanity_check', False):
|
||||
self._sanity_check()
|
||||
|
||||
self.message_subscribe([self.env.user.partner_id.id])
|
||||
|
||||
# Run the pre-validation wizards. Processing a pre-validation wizard should work on the
|
||||
# moves and/or the context and never call `_action_done`.
|
||||
if not self.env.context.get('button_validate_picking_ids'):
|
||||
self = self.with_context(button_validate_picking_ids=self.ids)
|
||||
res = self._pre_action_done_hook()
|
||||
if res is not True:
|
||||
return res
|
||||
|
||||
# Call `_action_done`.
|
||||
pickings_not_to_backorder = self.filtered(lambda p: p.picking_type_id.create_backorder == 'never')
|
||||
if self.env.context.get('picking_ids_not_to_backorder'):
|
||||
pickings_not_to_backorder |= self.browse(self.env.context['picking_ids_not_to_backorder']).filtered(
|
||||
lambda p: p.picking_type_id.create_backorder != 'always'
|
||||
)
|
||||
pickings_to_backorder = self - pickings_not_to_backorder
|
||||
pickings_not_to_backorder.with_context(cancel_backorder=True)._action_done()
|
||||
pickings_to_backorder.with_context(cancel_backorder=False)._action_done()
|
||||
|
||||
if self.user_has_groups('stock.group_reception_report') \
|
||||
and self.picking_type_id.auto_show_reception_report:
|
||||
lines = self.move_ids.filtered(lambda
|
||||
m: m.product_id.type == 'product' and m.state != 'cancel' and m.quantity_done and not m.move_dest_ids)
|
||||
if lines:
|
||||
# don't show reception report if all already assigned/nothing to assign
|
||||
wh_location_ids = self.env['stock.location']._search(
|
||||
[('id', 'child_of', self.picking_type_id.warehouse_id.view_location_id.ids),
|
||||
('usage', '!=', 'supplier')])
|
||||
if self.env['stock.move'].search([
|
||||
('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']),
|
||||
('product_qty', '>', 0),
|
||||
('location_id', 'in', wh_location_ids),
|
||||
('move_orig_ids', '=', False),
|
||||
('picking_id', 'not in', self.ids),
|
||||
('product_id', 'in', lines.product_id.ids)], limit=1):
|
||||
action = self.action_view_reception_report()
|
||||
action['context'] = {'default_picking_ids': self.ids}
|
||||
return action
|
||||
|
||||
if self.transfer_id:
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Internal Transfer'),
|
||||
'res_model': 'internal.transfer',
|
||||
'res_id': self.transfer_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
return True
|
||||
26
addons/tk_construction_management/models/con_template.py
Normal file
26
addons/tk_construction_management/models/con_template.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ConstructionTemplate(models.Model):
|
||||
_name = 'construction.product.template'
|
||||
_description = "Product Template"
|
||||
|
||||
name = fields.Char(string="Title")
|
||||
template_ids = fields.One2many('construction.template.line', 'template_id')
|
||||
|
||||
|
||||
class ConstructionTemplateLine(models.Model):
|
||||
_name = 'construction.template.line'
|
||||
_description = "Product Template Line"
|
||||
|
||||
product_id = fields.Many2one('product.product', string="Product", domain="[('is_material','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
template_id = fields.Many2one('construction.product.template')
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
33
addons/tk_construction_management/models/configuration.py
Normal file
33
addons/tk_construction_management/models/configuration.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models
|
||||
|
||||
|
||||
class DocumentType(models.Model):
|
||||
_name = 'site.document.type'
|
||||
_description = "Project Document Type"
|
||||
|
||||
name = fields.Char(string="Title")
|
||||
|
||||
|
||||
class JobType(models.Model):
|
||||
_name = 'job.type'
|
||||
_description = "Activity"
|
||||
|
||||
name = fields.Char(string="Title")
|
||||
sub_category_ids = fields.Many2many('job.sub.category', string="Work Sub Type")
|
||||
|
||||
|
||||
class JobSubCategory(models.Model):
|
||||
_name = 'job.sub.category'
|
||||
_description = "Sub Activity"
|
||||
|
||||
name = fields.Char(string="Title")
|
||||
|
||||
|
||||
class InsuranceRisk(models.Model):
|
||||
_name = 'insurance.risk'
|
||||
_description = "Insurance Risk"
|
||||
|
||||
name = fields.Char(string="Risk")
|
||||
@@ -0,0 +1,245 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class ConstructionDashboard(models.Model):
|
||||
_name = 'tk.construction.dashboard'
|
||||
_description = "Construction Dashboard"
|
||||
|
||||
name = fields.Char()
|
||||
|
||||
@api.model
|
||||
def get_construction_state(self, site_id, project_id):
|
||||
site_domain = []
|
||||
project_domain = []
|
||||
int_domain = []
|
||||
if (not site_id and not project_id) or (site_id == 'all_site' and project_id == 'all_project'):
|
||||
pass
|
||||
elif not site_id == 'all_site' and project_id == 'all_project':
|
||||
site_domain = [('construction_site_id', '=', int(site_id))]
|
||||
int_domain = [('site_id', '=', int(site_id))]
|
||||
projects = self.env['tk.construction.project'].search(
|
||||
site_domain).mapped('id')
|
||||
project_domain = [('project_id', 'in', projects)]
|
||||
elif site_id and project_id:
|
||||
site_domain = [('construction_site_id', '=', int(site_id))]
|
||||
int_domain = [('site_id', '=', int(site_id))]
|
||||
project_domain = [('project_id', '=', int(project_id))]
|
||||
total_site = self.env['tk.construction.site'].search_count([])
|
||||
total_project = self.env['tk.construction.project'].search_count(
|
||||
site_domain)
|
||||
total_mrq = self.env['material.requisition'].search_count(
|
||||
project_domain)
|
||||
job_sheet_count = self.env['job.costing'].search_count(project_domain)
|
||||
job_order_count = self.env['job.order'].search_count(project_domain)
|
||||
back_order = self.env['material.requisition'].sudo().search_count(
|
||||
[('is_back_order', '=', True)] + project_domain)
|
||||
forward_transfer = self.env['internal.transfer'].sudo().search_count(
|
||||
[('is_forward_transfer', '=', True)] + int_domain)
|
||||
return {
|
||||
'total_site': total_site,
|
||||
'total_project': total_project,
|
||||
'total_mrq': total_mrq,
|
||||
'job_sheet_count': job_sheet_count,
|
||||
'job_order_count': job_order_count,
|
||||
'mrq_state': self.get_mrq_state(project_domain),
|
||||
'site_state': self.get_site_state(),
|
||||
'internal_state': self.get_it_state(int_domain),
|
||||
'site_timeline': self.construction_time_line(),
|
||||
'project_timeline': self.project_time_line(site_domain),
|
||||
'project_status': self.get_project_status(site_domain),
|
||||
'job_order_po': self.get_purchase_order(project_domain),
|
||||
'back_order': back_order,
|
||||
'forward_transfer': forward_transfer,
|
||||
'purchase_order': self.get_purchase_order_state(project_domain),
|
||||
'con_sites': self.get_site_list(),
|
||||
}
|
||||
|
||||
@api.model
|
||||
def get_project_list(self, site_domain):
|
||||
projects = {}
|
||||
if not site_domain == 'all_site':
|
||||
domain = [('construction_site_id', '=', int(site_domain))]
|
||||
project_obj = self.env['tk.construction.project'].sudo()
|
||||
for rec in project_obj.search(domain):
|
||||
projects[rec.id] = rec.name
|
||||
return projects
|
||||
if site_domain == 'all_site':
|
||||
return projects
|
||||
|
||||
def get_site_list(self):
|
||||
site_obj = self.env['tk.construction.site'].sudo()
|
||||
sites = {}
|
||||
for rec in site_obj.search([]):
|
||||
sites[rec.id] = rec.name
|
||||
return sites
|
||||
|
||||
def get_mrq_state(self, project_domain):
|
||||
material_requisition = self.env['material.requisition'].sudo()
|
||||
draft = material_requisition.search_count(
|
||||
[('stage', '=', 'draft')] + project_domain)
|
||||
waiting_approval = material_requisition.search_count(
|
||||
[('stage', '=', 'department_approval')] + project_domain)
|
||||
approved = material_requisition.search_count(
|
||||
[('stage', '=', 'approve')] + project_domain)
|
||||
ready_delivery = material_requisition.search_count(
|
||||
[('stage', '=', 'ready_delivery')] + project_domain)
|
||||
material_arrive = material_requisition.search_count(
|
||||
[('stage', '=', 'material_arrived')] + project_domain)
|
||||
internal_transfer = material_requisition.search_count(
|
||||
[('stage', '=', 'internal_transfer')] + project_domain)
|
||||
reject = material_requisition.search_count(
|
||||
[('stage', '=', 'reject')] + project_domain)
|
||||
cancel = material_requisition.search_count(
|
||||
[('stage', '=', 'cancel')] + project_domain)
|
||||
return [
|
||||
['Draft', 'Waiting Approval', 'In Progress', 'Ready Delivery', 'Material Arrive', 'Internal Transfer',
|
||||
'Reject', 'Cancel'],
|
||||
[draft, waiting_approval, approved, ready_delivery,
|
||||
material_arrive, internal_transfer, reject, cancel]
|
||||
]
|
||||
|
||||
def get_site_state(self):
|
||||
site = self.env['tk.construction.site'].sudo()
|
||||
draft = site.search_count([('status', '=', 'draft')])
|
||||
in_progress = site.search_count([('status', '=', 'in_progress')])
|
||||
done = site.search_count([('status', '=', 'complete')])
|
||||
return [
|
||||
['Draft', 'In Progress', 'Complete'], [draft, in_progress, done]
|
||||
]
|
||||
|
||||
def get_it_state(self, site_domain):
|
||||
internal_transfer = self.env['internal.transfer'].sudo()
|
||||
draft = internal_transfer.search_count(
|
||||
[('stage', '=', 'draft')] + site_domain)
|
||||
in_progress = internal_transfer.search_count(
|
||||
[('stage', '=', 'in_progress')] + site_domain)
|
||||
done = internal_transfer.search_count(
|
||||
[('stage', '=', 'done')] + site_domain)
|
||||
return [
|
||||
['Draft', 'In Progress', 'Done'], [draft, in_progress, done]
|
||||
]
|
||||
|
||||
def construction_time_line(self):
|
||||
site_data = []
|
||||
construction_site_data = self.env['tk.construction.site'].search([])
|
||||
for site in construction_site_data:
|
||||
if site.status == "in_progress":
|
||||
site_data.append({
|
||||
'name': site.name,
|
||||
'start_date': str(site.start_date),
|
||||
'end_date': str(site.end_date),
|
||||
})
|
||||
return site_data
|
||||
|
||||
def project_time_line(self, site_domain):
|
||||
data = []
|
||||
project_data = self.env['tk.construction.project'].search(site_domain)
|
||||
for p in project_data:
|
||||
if p.stage == "Construction":
|
||||
data.append({
|
||||
'name': str(p.name) + " - " + str(p.construction_site_id.name),
|
||||
'start_date': str(p.start_date),
|
||||
'end_date': str(p.end_date),
|
||||
})
|
||||
return data
|
||||
|
||||
def get_project_status(self, site_domain):
|
||||
project_obj = self.env['tk.construction.project'].sudo()
|
||||
planning = project_obj.search_count(
|
||||
[('stage', '=', 'Planning')] + site_domain)
|
||||
procurement = project_obj.search_count(
|
||||
[('stage', '=', 'Procurement')] + site_domain)
|
||||
construction = project_obj.search_count(
|
||||
[('stage', '=', 'Construction')] + site_domain)
|
||||
handover = project_obj.search_count(
|
||||
[('stage', '=', 'Handover')] + site_domain)
|
||||
return [
|
||||
['Planning', 'Procurement', 'Construction', 'Handover'],
|
||||
[planning, procurement, construction, handover]
|
||||
]
|
||||
|
||||
def get_purchase_order(self, project_domain):
|
||||
jo_order = []
|
||||
mrq_po = []
|
||||
equip_po = []
|
||||
labour_po = []
|
||||
overhead_po = []
|
||||
job_order = self.env['job.order'].sudo().search(project_domain)
|
||||
for j in job_order:
|
||||
if j.project_id.stage == "Construction":
|
||||
name = str(j.name) + " - " + str(j.title)
|
||||
jo_order.append(name)
|
||||
mrq_po.append(j.material_req_id.po_count)
|
||||
equip_po.append(j.equip_po_count)
|
||||
labour_po.append(j.labour_po_count)
|
||||
overhead_po.append(j.overhead_po_count)
|
||||
data = [jo_order, mrq_po, equip_po, labour_po, overhead_po]
|
||||
return data
|
||||
|
||||
def get_purchase_order_state(self, project_domain):
|
||||
domain = project_domain
|
||||
po_obj = self.env['purchase.order'].sudo()
|
||||
mr_po = po_obj.search_count(
|
||||
[('material_req_id', '!=', False)] + domain)
|
||||
equip_po = 0
|
||||
labour_po = 0
|
||||
overhead_po = 0
|
||||
job_orders = self.env['job.order'].search(domain)
|
||||
for rec in job_orders:
|
||||
equip_po = equip_po + po_obj.search_count(
|
||||
[('job_order_id', '=', rec.id), ('purchase_order', '=', 'equipment')])
|
||||
labour_po = labour_po + po_obj.search_count(
|
||||
[('job_order_id', '=', rec.id), ('purchase_order', '=', 'labour')])
|
||||
overhead_po = overhead_po + po_obj.search_count(
|
||||
[('job_order_id', '=', rec.id), ('purchase_order', '=', 'overhead')])
|
||||
return {
|
||||
'mr_po': mr_po,
|
||||
'equip_po': equip_po,
|
||||
'labour_po': labour_po,
|
||||
'overhead_po': overhead_po,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def get_construction_project_domain(self, site_id, project_id):
|
||||
site_domain = []
|
||||
project_domain = []
|
||||
if site_id == 'all_site':
|
||||
pass
|
||||
elif not site_id == 'all_site' and project_id == 'all_project':
|
||||
site_domain = [('construction_site_id', '=', int(site_id))]
|
||||
projects = self.env['tk.construction.project'].search(
|
||||
site_domain).mapped('id')
|
||||
project_domain = [('project_id', 'in', projects)]
|
||||
elif site_id and project_id:
|
||||
site_domain = [('construction_site_id', '=', int(site_id))]
|
||||
project_domain = [('project_id', '=', int(project_id))]
|
||||
return [site_domain, project_domain]
|
||||
|
||||
@api.model
|
||||
def get_job_order_po(self, site_id, project_id, source):
|
||||
project_domain = []
|
||||
if site_id == 'all_site':
|
||||
pass
|
||||
elif not site_id == 'all_site' and project_id == 'all_project':
|
||||
site_domain = [('construction_site_id', '=', int(site_id))]
|
||||
projects = self.env['tk.construction.project'].search(
|
||||
site_domain).mapped('id')
|
||||
project_domain = [('project_id', 'in', projects)]
|
||||
elif site_id and project_id:
|
||||
project_domain = [('project_id', '=', int(project_id))]
|
||||
ids = []
|
||||
po_obj = self.env['purchase.order'].sudo()
|
||||
for data in self.env['job.order'].search(project_domain):
|
||||
if source == 'equip':
|
||||
ids = ids + po_obj.search(
|
||||
[('job_order_id', '=', data.id), ('purchase_order', '=', 'equipment')]).mapped('id')
|
||||
if source == 'labour':
|
||||
ids = ids + po_obj.search(
|
||||
[('job_order_id', '=', data.id), ('purchase_order', '=', 'labour')]).mapped('id')
|
||||
if source == 'overhead':
|
||||
ids = ids + po_obj.search(
|
||||
[('job_order_id', '=', data.id), ('purchase_order', '=', 'overhead')]).mapped('id')
|
||||
return ids
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ConstructionEmployee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
846
addons/tk_construction_management/models/construction_project.py
Normal file
846
addons/tk_construction_management/models/construction_project.py
Normal file
@@ -0,0 +1,846 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
import xlwt
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from odoo import fields, api, models, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class ConstructionProject(models.Model):
|
||||
_name = 'tk.construction.project'
|
||||
_description = "Construction Project"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string="Title", tracking=True)
|
||||
start_date = fields.Date(string="Schedule Start Date", tracking=True)
|
||||
end_date = fields.Date(string="Schedule End Date", tracking=True)
|
||||
project_progress = fields.Float(
|
||||
related="budget_id.progress", string="Project Progress", tracking=True)
|
||||
construction_site_id = fields.Many2one(
|
||||
'tk.construction.site', string="Project ", tracking=True)
|
||||
responsible_id = fields.Many2one('res.users', default=lambda self: self.env.user and self.env.user.id or False,
|
||||
string="Created By")
|
||||
stage = fields.Selection([('draft', 'Draft'),
|
||||
('Planning', 'Planning'),
|
||||
('Procurement', 'Procurement'),
|
||||
('Construction', 'Construction'),
|
||||
('Handover', 'Handover')], string="Stage", default="draft", tracking=True)
|
||||
warehouse_id = fields.Many2one(
|
||||
'stock.warehouse', string="Warehouse", tracking=True)
|
||||
code = fields.Char(string="Code", tracking=True)
|
||||
project_id = fields.Many2one(
|
||||
'project.project', string="Project", tracking=True)
|
||||
engineer_ids = fields.Many2many(
|
||||
'hr.employee', string="Engineers", tracking=True)
|
||||
|
||||
# Department
|
||||
department_id = fields.Many2one(
|
||||
'construction.department', string="Department", tracking=True)
|
||||
manager_ids = fields.Many2many('res.users', string="Manager")
|
||||
user_id = fields.Many2one('res.users', string="Responsible")
|
||||
|
||||
# Address
|
||||
zip = fields.Char(string='Pin Code')
|
||||
street = fields.Char(string='Street1')
|
||||
street2 = fields.Char(string='Street2')
|
||||
city = fields.Char(string='City')
|
||||
country_id = fields.Many2one('res.country', 'Country')
|
||||
state_id = fields.Many2one(
|
||||
"res.country.state", string='State', readonly=False, store=True,
|
||||
domain="[('country_id', '=?', country_id)]")
|
||||
longitude = fields.Char(string="Longitude")
|
||||
latitude = fields.Char(string="Latitude")
|
||||
|
||||
# BOQ Details
|
||||
is_use_measure = fields.Boolean(
|
||||
string="Is use of (LENGTH x WIDTH x HEIGHT ) ?")
|
||||
boq_budget_ids = fields.One2many(
|
||||
'boq.budget', 'project_id', string="Budget ")
|
||||
|
||||
# Budget
|
||||
budget_id = fields.Many2one('sub.project.budget', string="Budget")
|
||||
|
||||
# One2Many
|
||||
document_ids = fields.One2many(
|
||||
'project.documents', 'project_id', string="Documents")
|
||||
policy_ids = fields.One2many(
|
||||
'project.insurance', 'project_id', string="Insurance")
|
||||
expense_ids = fields.One2many(
|
||||
'extra.expense', 'project_id', string="Expense")
|
||||
phase_ids = fields.One2many('job.costing', 'project_id')
|
||||
|
||||
# Count & Totals
|
||||
job_sheet_count = fields.Integer(
|
||||
string="Project Phase Count", compute="_compute_count")
|
||||
job_order_count = fields.Integer(
|
||||
string="Work Order Count", compute="_compute_count")
|
||||
mrq_count = fields.Integer(string="MRQ Count", compute="_compute_count")
|
||||
mrq_po_count = fields.Integer(
|
||||
string="MRQ PO Count", compute="_compute_count")
|
||||
jo_po_count = fields.Integer(
|
||||
string="JO PO Count", compute="_compute_count")
|
||||
inspection_task_count = fields.Integer(
|
||||
string="Inspection Task Count", compute="_compute_count")
|
||||
task_count = fields.Integer(string="Task Count", compute="_compute_count")
|
||||
budget_count = fields.Integer(
|
||||
string="Budget Count", compute="_compute_count")
|
||||
|
||||
# Create, Write, Unlink, Constrain
|
||||
@api.model_create_multi
|
||||
def create(self, vals):
|
||||
res = super(ConstructionProject, self).create(vals)
|
||||
for rec in res:
|
||||
data = {
|
||||
'name': rec.name + "'s Project",
|
||||
'construction_project_id': rec.id,
|
||||
'company_id': self.env.company.id,
|
||||
'privacy_visibility': 'followers'
|
||||
}
|
||||
project_id = self.env['project.project'].create(data)
|
||||
rec.project_id = project_id.id
|
||||
task_stage = self.env['project.task.type'].sudo().search(
|
||||
[('active', '=', True), ('user_id', '=', False)])
|
||||
ids = []
|
||||
for data in task_stage:
|
||||
ids = data.project_ids.ids
|
||||
ids.append(project_id.id)
|
||||
data.project_ids = ids
|
||||
return res
|
||||
|
||||
@api.constrains('boq_budget_ids')
|
||||
def _check_sub_activity(self):
|
||||
for rec in self:
|
||||
activity_counts = {}
|
||||
for activity in rec.boq_budget_ids:
|
||||
key = (activity.activity_id.id, activity.sub_activity_id.id)
|
||||
if key in activity_counts:
|
||||
activity_counts[key] += 1
|
||||
else:
|
||||
activity_counts[key] = 1
|
||||
duplicate = [pair for pair,
|
||||
count in activity_counts.items() if count > 1]
|
||||
if duplicate:
|
||||
raise ValidationError(
|
||||
"Duplicate work type and sub work type pair found.")
|
||||
|
||||
@api.onchange('construction_site_id')
|
||||
def onchange_site_address(self):
|
||||
for rec in self:
|
||||
if rec.construction_site_id:
|
||||
rec.zip = rec.construction_site_id.zip
|
||||
rec.street = rec.construction_site_id.street
|
||||
rec.street2 = rec.construction_site_id.street2
|
||||
rec.city = rec.construction_site_id.city
|
||||
rec.state_id = rec.construction_site_id.state_id.id
|
||||
rec.country_id = rec.construction_site_id.country_id.id
|
||||
rec.start_date = rec.construction_site_id.start_date
|
||||
rec.end_date = rec.construction_site_id.end_date
|
||||
|
||||
# Compute
|
||||
def _compute_count(self):
|
||||
for rec in self:
|
||||
rec.job_sheet_count = self.env['job.costing'].search_count(
|
||||
[('project_id', '=', rec.id)])
|
||||
rec.job_order_count = self.env['job.order'].search_count(
|
||||
[('project_id', '=', rec.id)])
|
||||
rec.mrq_count = self.env['material.requisition'].search_count(
|
||||
[('project_id', '=', rec.id)])
|
||||
rec.mrq_po_count = self.env['purchase.order'].search_count(
|
||||
[('project_id', '=', rec.id)])
|
||||
job_order_ids = self.env['job.order'].search(
|
||||
[('project_id', '=', self.id)]).mapped('id')
|
||||
po_ids = self.env['purchase.order'].search(
|
||||
[('job_order_id', 'in', job_order_ids)]).mapped('id')
|
||||
rec.jo_po_count = len(po_ids)
|
||||
rec.task_count = self.env['project.task'].search_count(
|
||||
[('con_project_id', '=', rec.id), ('is_inspection_task', '=', False)])
|
||||
rec.inspection_task_count = self.env['project.task'].search_count(
|
||||
[('con_project_id', '=', rec.id), ('is_inspection_task', '=', True)])
|
||||
rec.budget_count = self.env['project.budget'].search_count(
|
||||
[('sub_project_budget_id', '=', rec.budget_id.id)])
|
||||
|
||||
# Onchange
|
||||
@api.onchange('department_id', 'manager_ids')
|
||||
def _onchange_department_manager(self):
|
||||
ids = []
|
||||
for rec in self:
|
||||
if rec.department_id:
|
||||
ids = rec.department_id.manager_ids.ids
|
||||
return {'domain': {'manager_ids': [('id', 'in', ids)]}}
|
||||
|
||||
@api.onchange('department_id', 'manager_ids', 'user_id')
|
||||
def _onchange_department_responsible(self):
|
||||
ids = []
|
||||
for rec in self:
|
||||
if rec.department_id:
|
||||
ids = rec.department_id.user_ids.ids
|
||||
return {'domain': {'user_id': [('id', 'in', ids)]}}
|
||||
|
||||
# Smart Button
|
||||
def action_gmap_location(self):
|
||||
if self.longitude and self.latitude:
|
||||
longitude = self.longitude
|
||||
latitude = self.latitude
|
||||
http_url = 'https://maps.google.com/maps?q=loc:' + latitude + ',' + longitude
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'target': 'new',
|
||||
'url': http_url,
|
||||
}
|
||||
else:
|
||||
raise ValidationError(
|
||||
_("! Enter Proper Longitude and Latitude Values"))
|
||||
|
||||
def action_view_job_sheet(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Project Phase(WBS)'),
|
||||
'res_model': 'job.costing',
|
||||
'domain': [('project_id', '=', self.id)],
|
||||
'context': {'default_project_id': self.id},
|
||||
'view_mode': 'tree,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_job_order(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Work Order'),
|
||||
'res_model': 'job.order',
|
||||
'domain': [('project_id', '=', self.id)],
|
||||
'context': {'default_project_id': self.id},
|
||||
'view_mode': 'tree,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_material_requisition(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Material Requisition'),
|
||||
'res_model': 'material.requisition',
|
||||
'domain': [('project_id', '=', self.id)],
|
||||
'context': {'default_project_id': self.id},
|
||||
'view_mode': 'tree,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_mrq_purchase_orders(self):
|
||||
ids = self.env['purchase.order'].search(
|
||||
[('project_id', '=', self.id)]).mapped('id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('MRQ Purchase Order'),
|
||||
'res_model': 'purchase.order',
|
||||
'domain': [('id', 'in', ids)],
|
||||
'context': {'create': False},
|
||||
'view_mode': 'tree,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_jo_purchase_orders(self):
|
||||
job_order_ids = self.env['job.order'].search(
|
||||
[('project_id', '=', self.id)]).mapped('id')
|
||||
po_ids = self.env['purchase.order'].search(
|
||||
[('job_order_id', 'in', job_order_ids)]).mapped('id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Work Order PO'),
|
||||
'res_model': 'purchase.order',
|
||||
'domain': [('id', 'in', po_ids)],
|
||||
'context': {'create': False, 'group_by': 'purchase_order'},
|
||||
'view_mode': 'tree,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_project_task(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Task'),
|
||||
'res_model': 'project.task',
|
||||
'domain': [('con_project_id', '=', self.id), ('is_inspection_task', '=', False)],
|
||||
'context': {'default_con_project_id': self.id, 'default_project_id': self.project_id.id},
|
||||
'view_mode': 'kanban,tree,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_project_task_inspection(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Inspection Task'),
|
||||
'res_model': 'project.task',
|
||||
'domain': [('con_project_id', '=', self.id), ('is_inspection_task', '=', True)],
|
||||
'context': {'default_con_project_id': self.id, 'default_project_id': self.project_id.id,
|
||||
'default_is_inspection_task': True, 'default_priority': '1'},
|
||||
'view_mode': 'tree,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_budget(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Budget'),
|
||||
'res_model': 'project.budget',
|
||||
'domain': [('sub_project_budget_id', '=', self.budget_id.id)],
|
||||
'context': {'default_sub_project_budget_id': self.budget_id.id},
|
||||
'view_mode': 'tree,kanban,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
# Button
|
||||
def action_project_planning(self):
|
||||
self.stage = 'Planning'
|
||||
|
||||
def action_stage_procurement(self):
|
||||
if not self.warehouse_id:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "Please choose a warehouse to proceed to the next stage of construction process",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
if not self.budget_id:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "Please create a budget to proceed to the next stage of construction process",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
|
||||
self.stage = 'Procurement'
|
||||
|
||||
def action_stage_construction(self):
|
||||
self.stage = 'Construction'
|
||||
|
||||
def action_stage_handover(self):
|
||||
phase_completed = True
|
||||
phase_record = self.env['job.costing'].search(
|
||||
[('project_id', '=', self.id)])
|
||||
for data in phase_record:
|
||||
if data.status != 'complete':
|
||||
phase_completed = False
|
||||
break
|
||||
if not phase_completed:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "Please complete all the phases related to this project to handover this project.",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
self.stage = 'Handover'
|
||||
|
||||
def get_float_time(self, t):
|
||||
hour, minute = divmod(t, 1)
|
||||
minute *= 60
|
||||
result = '{}:{}'.format(int(hour), int(minute))
|
||||
return result
|
||||
|
||||
def action_print_budget_excel_report(self):
|
||||
workbook = xlwt.Workbook(encoding='utf-8')
|
||||
sheet1 = workbook.add_sheet('Budget Details', cell_overwrite_ok=True)
|
||||
sheet1.show_grid = False
|
||||
sheet1.col(0).width = 400
|
||||
sheet1.col(3).width = 600
|
||||
sheet1.col(9).width = 600
|
||||
sheet1.row(4).height = 400
|
||||
sheet1.row(5).height = 200
|
||||
sheet1.row(6).height = 400
|
||||
sheet1.row(7).height = 200
|
||||
sheet1.row(8).height = 400
|
||||
sheet1.row(9).height = 200
|
||||
sheet1.row(10).height = 400
|
||||
sheet1.row(11).height = 200
|
||||
sheet1.row(12).height = 400
|
||||
sheet1.row(17).height = 400
|
||||
sheet1.col(12).width = 5000
|
||||
sheet1.col(13).width = 4000
|
||||
sheet1.col(14).width = 5000
|
||||
sheet1.col(15).width = 5000
|
||||
sheet1.col(16).width = 8000
|
||||
sheet1.col(17).width = 7000
|
||||
sheet1.col(18).width = 7000
|
||||
sheet1.col(19).width = 7000
|
||||
sheet1.col(20).width = 7000
|
||||
sheet1.col(21).width = 7000
|
||||
sheet1.col(22).width = 5000
|
||||
sheet1.row(14).height = 600
|
||||
border_squre = xlwt.Borders()
|
||||
border_squre.bottom = xlwt.Borders.HAIR
|
||||
border_squre.bottom_colour = xlwt.Style.colour_map["sea_green"]
|
||||
al = xlwt.Alignment()
|
||||
al.horz = xlwt.Alignment.HORZ_LEFT
|
||||
al.vert = xlwt.Alignment.VERT_CENTER
|
||||
date_format = xlwt.XFStyle()
|
||||
date_format.num_format_str = 'mm/dd/yyyy'
|
||||
date_format.font.name = "Century Gothic"
|
||||
date_format.borders = border_squre
|
||||
date_format.alignment = al
|
||||
date_format.font.colour_index = 0x36
|
||||
date_format.font.bold = 1
|
||||
sheet1.set_panes_frozen(True)
|
||||
sheet1.set_horz_split_pos(18)
|
||||
sheet1.remove_splits = True
|
||||
title = xlwt.easyxf(
|
||||
"font: height 350, name Century Gothic, bold on, color_index blue_gray;"
|
||||
" align: vert center, horz center;"
|
||||
"border: bottom thick, bottom_color sea_green;")
|
||||
values = xlwt.easyxf(
|
||||
"font:name Century Gothic, bold on, color_index blue_gray;"
|
||||
" align: vert center, horz left;"
|
||||
"border: bottom hair, bottom_color sea_green;")
|
||||
line_percentage_value = xlwt.easyxf(
|
||||
"font:name Century Gothic, bold on, color_index blue_gray;"
|
||||
" align: vert center, horz center;"
|
||||
"border: bottom hair, bottom_color blue_gray, right hair,right_color blue_gray;")
|
||||
line_values = xlwt.easyxf(
|
||||
"font:name Century Gothic;"
|
||||
" align: vert center, horz center;"
|
||||
"border: bottom hair, bottom_color blue_gray, right hair,right_color blue_gray,left hair,left_color blue_gray;")
|
||||
line_amount_values = xlwt.easyxf(
|
||||
"font:name Century Gothic;"
|
||||
" align: vert center, horz right;"
|
||||
"border: bottom hair, bottom_color blue_gray, right hair,right_color blue_gray,left hair,left_color blue_gray;")
|
||||
line_amount_sub_title = xlwt.easyxf(
|
||||
"font: height 185, name Century Gothic, bold on, color_index gray80; "
|
||||
"align: vert center, horz right; "
|
||||
"border: top hair, bottom hair, left hair, right hair, "
|
||||
"top_color gray50, bottom_color gray50, left_color gray50, right_color gray50")
|
||||
sub_title = xlwt.easyxf(
|
||||
"font: height 185, name Century Gothic, bold on, color_index gray80; "
|
||||
"align: vert center, horz center; "
|
||||
"border: top hair, bottom hair, left hair, right hair, "
|
||||
"top_color gray50, bottom_color gray50, left_color gray50, right_color gray50")
|
||||
# Budget Details
|
||||
budget_amount = str(self.budget_id.currency_id.symbol) + \
|
||||
" "+str(self.budget_id.total_budget_amount)
|
||||
utilization_amount = str(self.budget_id.currency_id.symbol) + " " + str(
|
||||
self.budget_id.utilization_amount)
|
||||
utilization_percentage = str(self.budget_id.progress) + " %"
|
||||
sheet1.write_merge(0, 2, 1, 11, "Budget Details", title)
|
||||
sheet1.write_merge(4, 4, 1, 2, "Project", sub_title)
|
||||
sheet1.write_merge(4, 4, 4, 5, self.construction_site_id.name, values)
|
||||
sheet1.write_merge(6, 6, 1, 2, "Sub Project", sub_title)
|
||||
sheet1.write_merge(6, 6, 4, 5, self.name, values)
|
||||
sheet1.write_merge(4, 4, 7, 8, "Start Date", sub_title)
|
||||
sheet1.write_merge(
|
||||
4, 4, 10, 11, self.budget_id.start_date, date_format)
|
||||
sheet1.write_merge(6, 6, 7, 8, "End Date", sub_title)
|
||||
sheet1.write_merge(6, 6, 10, 11, self.budget_id.end_date, date_format)
|
||||
sheet1.write_merge(8, 8, 1, 2, "Total Budget Amount", sub_title)
|
||||
sheet1.write_merge(8, 8, 4, 5, budget_amount, values)
|
||||
sheet1.write_merge(10, 10, 1, 2, "Budget Utilization", sub_title)
|
||||
sheet1.write_merge(10, 10, 4, 5, utilization_amount, values)
|
||||
sheet1.write_merge(12, 12, 1, 2, "Utilization(%)", sub_title)
|
||||
sheet1.write_merge(12, 12, 4, 5, utilization_percentage, values)
|
||||
# Budget Lines
|
||||
sheet1.write_merge(14, 15, 1, 22, "Budget Lines", title)
|
||||
sheet1.write_merge(17, 17, 1, 3, "Work Type", sub_title)
|
||||
sheet1.write_merge(17, 17, 4, 6, "Work Sub Type", sub_title)
|
||||
sheet1.write(17, 7, "BOQ Qty", line_amount_sub_title)
|
||||
sheet1.write_merge(17, 17, 8, 10, "Additional Qty",
|
||||
line_amount_sub_title)
|
||||
sheet1.write(17, 11, "Total Qty", line_amount_sub_title)
|
||||
sheet1.write(17, 12, "Rate Analysis", sub_title)
|
||||
sheet1.write(17, 13, "Price / Qty", line_amount_sub_title)
|
||||
sheet1.write(17, 14, "Untaxed", line_amount_sub_title)
|
||||
sheet1.write(17, 15, "Tax Amount", line_amount_sub_title)
|
||||
sheet1.write(17, 16, "Total Budget", line_amount_sub_title)
|
||||
sheet1.write(17, 17, "Material Spent", line_amount_sub_title)
|
||||
sheet1.write(17, 18, "Equipment Spent", line_amount_sub_title)
|
||||
sheet1.write(17, 19, "Labour Spent", line_amount_sub_title)
|
||||
sheet1.write(17, 20, "Overhead Spent", line_amount_sub_title)
|
||||
sheet1.write(17, 21, "Remaining Budget", line_amount_sub_title)
|
||||
sheet1.write(17, 22, "Utilization(%)", sub_title)
|
||||
col = 18
|
||||
for data in self.budget_id.budget_line_ids:
|
||||
sheet1.row(col).height = 400
|
||||
sheet1.write_merge(
|
||||
col, col, 1, 3, data.job_type_id.name, line_values)
|
||||
sheet1.write_merge(
|
||||
col, col, 4, 6, data.sub_category_id.name, line_values)
|
||||
sheet1.write(col, 7, data.boq_qty, line_amount_values)
|
||||
sheet1.write_merge(
|
||||
col, col, 8, 10, data.additional_qty, line_amount_values)
|
||||
sheet1.write(col, 11, (data.boq_qty +
|
||||
data.additional_qty), line_amount_values)
|
||||
sheet1.write(col, 12, ((data.rate_analysis_id.name)
|
||||
if data.rate_analysis_id else ""), line_values)
|
||||
sheet1.write(col, 13, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.price_per_qty)), line_amount_values)
|
||||
sheet1.write(col, 14, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.untaxed_amount)), line_amount_values)
|
||||
sheet1.write(col, 15, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.tax_amount)), line_amount_values)
|
||||
sheet1.write(col, 16, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.budget)), line_amount_values)
|
||||
sheet1.write(col, 17, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.material_spent)), line_amount_values)
|
||||
sheet1.write(col, 18, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.equipment_spent)), line_amount_values)
|
||||
sheet1.write(col, 19, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.labour_spent)), line_amount_values)
|
||||
sheet1.write(col, 20, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.overhead_spent)), line_amount_values)
|
||||
sheet1.write(col, 21, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.remaining_budget)), line_amount_values)
|
||||
sheet1.write(
|
||||
col, 22, (str(data.total_spent) + " " + "%"), line_percentage_value)
|
||||
col = col + 1
|
||||
|
||||
# Budget Spent
|
||||
self.get_budget_spent(workbook=workbook)
|
||||
|
||||
# Print Report
|
||||
stream = BytesIO()
|
||||
workbook.save(stream)
|
||||
out = base64.encodebytes(stream.getvalue())
|
||||
attachment = self.env['ir.attachment'].sudo()
|
||||
filename = 'Budget Report' + ".xls"
|
||||
attachment_id = attachment.create(
|
||||
{'name': filename,
|
||||
'type': 'binary',
|
||||
'public': False,
|
||||
'datas': out})
|
||||
if attachment_id:
|
||||
report = {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/web/content/%s?download=true' % (attachment_id.id),
|
||||
'target': 'self',
|
||||
}
|
||||
return report
|
||||
|
||||
def get_budget_spent(self, workbook):
|
||||
title = xlwt.easyxf(
|
||||
"font: height 300, name Century Gothic, bold on, color_index blue_gray;"
|
||||
" align: vert center, horz center;"
|
||||
"border: bottom thick, bottom_color sea_green;")
|
||||
title2 = xlwt.easyxf(
|
||||
"font: height 250, name Century Gothic, bold on, color_index blue_gray;"
|
||||
" align: vert center, horz left;"
|
||||
"border: bottom thin, bottom_color sea_green;")
|
||||
title3 = xlwt.easyxf(
|
||||
"font: height 250, name Century Gothic, bold on;"
|
||||
" align: vert center, horz right;"
|
||||
"border: bottom thin, bottom_color sea_green;")
|
||||
line_amount_sub_title = xlwt.easyxf(
|
||||
"font: height 185, name Century Gothic, bold on, color_index gray80; "
|
||||
"align: vert center, horz right; "
|
||||
"border: top hair, bottom hair, left hair, right hair, "
|
||||
"top_color gray50, bottom_color gray50, left_color gray50, right_color gray50")
|
||||
line_values = xlwt.easyxf(
|
||||
"font:name Century Gothic;"
|
||||
" align: vert center, horz center;"
|
||||
"border: bottom hair, bottom_color blue_gray, right hair,right_color blue_gray,left hair,left_color blue_gray;")
|
||||
sub_title = xlwt.easyxf(
|
||||
"font: height 185, name Century Gothic, bold on, color_index gray80; "
|
||||
"align: vert center, horz center; "
|
||||
"border: top hair, bottom hair, left hair, right hair, "
|
||||
"top_color gray50, bottom_color gray50, left_color gray50, right_color gray50")
|
||||
sub_title2 = xlwt.easyxf(
|
||||
"font: height 200, name Century Gothic, bold on, color_index gray80; "
|
||||
"align: vert center, horz left; "
|
||||
"border: bottom hair,"
|
||||
"bottom_color sea_green;")
|
||||
sub_title_amount = xlwt.easyxf(
|
||||
"font: height 200, name Century Gothic, bold on, color_index gray80; "
|
||||
"align: vert center, horz right; "
|
||||
"border: bottom hair,"
|
||||
"bottom_color sea_green;")
|
||||
horiz_double_line = xlwt.easyxf("border: top double, top_color gray50")
|
||||
for data in self.budget_id.budget_line_ids:
|
||||
budget_phase_ids = self.env['job.costing'].search(
|
||||
[('project_id', '=', self.id), ('activity_id', '=', data.job_type_id.id)]).mapped('id')
|
||||
domain = [('project_id', '=', self.id), ('work_type_id', '=', data.job_type_id.id),
|
||||
('sub_category_id', '=',
|
||||
data.sub_category_id.id), ('state', '=', 'complete'),
|
||||
('job_sheet_id', 'in', budget_phase_ids)]
|
||||
material_spent_rec = self.env['order.material.line'].search(domain)
|
||||
equip_spent_rec = self.env['order.equipment.line'].search(domain)
|
||||
labour_spent_rec = self.env['order.labour.line'].search(domain)
|
||||
overhead_spent_rec = self.env['order.overhead.line'].search(domain)
|
||||
sheet_name = data.job_type_id.name + \
|
||||
"(" + data.sub_category_id.name + ")"
|
||||
sheet = workbook.add_sheet(sheet_name, cell_overwrite_ok=True)
|
||||
sheet.show_grid = False
|
||||
row = 0
|
||||
sheet.row(4).height = 400
|
||||
sheet.row(6).height = 400
|
||||
sheet.write_merge(0, 2, 0, 6, sheet_name, title)
|
||||
sheet.write(4, 0, "Total Budget", sub_title2)
|
||||
sheet.write(4, 1, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.budget)), sub_title_amount)
|
||||
sheet.write_merge(4, 4, 3, 4, "Used Budget Amount", sub_title2)
|
||||
sheet.write_merge(4, 4, 5, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str((data.budget - data.remaining_budget))), sub_title_amount)
|
||||
sheet.write_merge(6, 6, 3, 4, "Remaining Budget", sub_title2)
|
||||
sheet.write_merge(6, 6, 5, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.remaining_budget)), sub_title_amount)
|
||||
|
||||
row = 5
|
||||
|
||||
# Material Rec
|
||||
sheet.write_merge(row+4, row+5, 0, 4, "Material Spent", title2)
|
||||
sheet.write_merge(row+4, row+5, 5, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.material_spent)), title3)
|
||||
sheet.col(0).width = 8000
|
||||
sheet.col(1).width = 8000
|
||||
sheet.col(2).width = 6500
|
||||
sheet.col(3).width = 5000
|
||||
sheet.col(4).width = 5000
|
||||
sheet.col(5).width = 5000
|
||||
sheet.col(6).width = 5000
|
||||
sheet.row(row+6).height = 600
|
||||
sheet.write(row+6, 0, "Project Phase(WBS)", sub_title)
|
||||
sheet.write(row+6, 1, "Work Order", sub_title)
|
||||
sheet.write(row+6, 2, "Material", sub_title)
|
||||
sheet.write(row+6, 3, "Qty.", line_amount_sub_title)
|
||||
sheet.write(row+6, 4, "Unit", sub_title)
|
||||
sheet.write(row+6, 5, "Price/Qty", line_amount_sub_title)
|
||||
sheet.write(row+6, 6, "Total Price", line_amount_sub_title)
|
||||
row = row + 7
|
||||
for rec in material_spent_rec:
|
||||
sheet.row(row).height = 400
|
||||
sheet.write(row, 0, (rec.job_sheet_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 1, (rec.job_order_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 2, rec.name, line_values)
|
||||
sheet.write(row, 3, rec.qty, line_amount_sub_title)
|
||||
sheet.write(row, 4, rec.uom_id.name, line_values)
|
||||
sheet.write(row, 5, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.price)), line_amount_sub_title)
|
||||
sheet.write(row, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.total_price)), line_amount_sub_title)
|
||||
row = row + 1
|
||||
sheet.write_merge(row, row, 0, 6, " ", horiz_double_line)
|
||||
row = row + 1
|
||||
|
||||
# Equipment Rec
|
||||
sheet.write_merge(row+1, row+2, 0, 4, "Equipment Spent", title2)
|
||||
sheet.write_merge(row+1, row+2, 5, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.equipment_spent)), title3)
|
||||
row = row + 3
|
||||
sheet.row(row).height = 600
|
||||
sheet.write(row, 0, "Project Phase(WBS)", sub_title)
|
||||
sheet.write(row, 1, "Work Order", sub_title)
|
||||
sheet.write(row, 2, "Vendor", sub_title)
|
||||
sheet.write(row, 3, "Equipment.", sub_title)
|
||||
sheet.write(row, 4, "Qty.", line_amount_sub_title)
|
||||
sheet.write(row, 5, "Price/Qty", line_amount_sub_title)
|
||||
sheet.write(row, 6, "Total Price", line_amount_sub_title)
|
||||
row = row+1
|
||||
for rec in equip_spent_rec:
|
||||
sheet.row(row).height = 400
|
||||
sheet.write(row, 0, (rec.job_sheet_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 1, (rec.job_order_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 2, rec.vendor_id.name, line_values)
|
||||
sheet.write(row, 3, rec.desc, line_values)
|
||||
sheet.write(row, 4, rec.qty, line_amount_sub_title)
|
||||
sheet.write(row, 5, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.cost)), line_amount_sub_title)
|
||||
sheet.write(row, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.total_cost)), line_amount_sub_title)
|
||||
row = row + 1
|
||||
sheet.write_merge(row, row, 0, 6, " ", horiz_double_line)
|
||||
row = row + 1
|
||||
|
||||
# Labour Rec
|
||||
sheet.write_merge(row+1, row+2, 0, 4, "Labour Spent", title2)
|
||||
sheet.write_merge(row+1, row+2, 5, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.labour_spent)), title3)
|
||||
row = row + 3
|
||||
sheet.row(row).height = 600
|
||||
sheet.write(row, 0, "Project Phase(WBS)", sub_title)
|
||||
sheet.write(row, 1, "Work Order", sub_title)
|
||||
sheet.write(row, 2, "Vendor", sub_title)
|
||||
sheet.write(row, 3, "Product", sub_title)
|
||||
sheet.write(row, 4, "Hours", line_amount_sub_title)
|
||||
sheet.write(row, 5, "Cost / Hour", line_amount_sub_title)
|
||||
sheet.write(row, 6, "Sub Total", line_amount_sub_title)
|
||||
row = row+1
|
||||
for rec in labour_spent_rec:
|
||||
sheet.row(row).height = 400
|
||||
sheet.write(row, 0, (rec.job_sheet_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 1, (rec.job_order_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 2, rec.vendor_id.name, line_values)
|
||||
sheet.write(row, 3, rec.name, line_values)
|
||||
sheet.write(row, 4, rec.hours, line_amount_sub_title)
|
||||
sheet.write(row, 5, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.cost)), line_amount_sub_title)
|
||||
sheet.write(row, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.sub_total)), line_amount_sub_title)
|
||||
row = row + 1
|
||||
sheet.write_merge(row, row, 0, 6, " ", horiz_double_line)
|
||||
row = row + 1
|
||||
|
||||
# Overhead Rec
|
||||
sheet.write_merge(row+1, row+2, 0, 4, "Overhead Spent", title2)
|
||||
sheet.write_merge(row+1, row+2, 5, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(data.overhead_spent)), title3)
|
||||
row = row + 3
|
||||
sheet.row(row).height = 600
|
||||
sheet.write(row, 0, "Project Phase(WBS)", sub_title)
|
||||
sheet.write(row, 1, "Work Order", sub_title)
|
||||
sheet.write(row, 2, "Vendor", sub_title)
|
||||
sheet.write(row, 3, "Product", sub_title)
|
||||
sheet.write(row, 4, "Qty.", line_amount_sub_title)
|
||||
sheet.write(row, 5, "Cost / Qty", line_amount_sub_title)
|
||||
sheet.write(row, 6, "Sub Total", line_amount_sub_title)
|
||||
row = row+1
|
||||
for rec in overhead_spent_rec:
|
||||
sheet.row(row).height = 400
|
||||
sheet.write(row, 0, (rec.job_sheet_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 1, (rec.job_order_id.name +
|
||||
" - " + rec.job_order_id.name), line_values)
|
||||
sheet.write(row, 2, rec.vendor_id.name, line_values)
|
||||
sheet.write(row, 3, rec.name, line_values)
|
||||
sheet.write(row, 4, rec.qty, line_amount_sub_title)
|
||||
sheet.write(row, 5, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.cost)), line_amount_sub_title)
|
||||
sheet.write(row, 6, (str(self.budget_id.currency_id.symbol) +
|
||||
" " + str(rec.sub_total)), line_amount_sub_title)
|
||||
row = row + 1
|
||||
sheet.write_merge(row, row, 0, 6, " ", horiz_double_line)
|
||||
row = row + 1
|
||||
|
||||
|
||||
class ProjectDocuments(models.Model):
|
||||
_name = 'project.documents'
|
||||
_description = "Project Documents"
|
||||
_rec_name = 'file_name'
|
||||
|
||||
document_type_id = fields.Many2one('site.document.type', string="Document")
|
||||
document = fields.Binary(string='Documents', required=True)
|
||||
file_name = fields.Char(string='File Name')
|
||||
project_id = fields.Many2one('tk.construction.project')
|
||||
|
||||
|
||||
class ProjectInsurance(models.Model):
|
||||
_name = 'project.insurance'
|
||||
_description = "Project Insurance"
|
||||
|
||||
name = fields.Char(string="Insurance")
|
||||
policy_no = fields.Char(string="Insurance No")
|
||||
risk_ids = fields.Many2many('insurance.risk', string="Risk Covered")
|
||||
document = fields.Binary(string='Documents')
|
||||
file_name = fields.Char(string='File Name')
|
||||
project_id = fields.Many2one('tk.construction.project')
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string="Company", default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
total_charge = fields.Monetary(string="Total Charge")
|
||||
issue_date = fields.Date(string="Issue Date", default=fields.Date.today())
|
||||
bill_id = fields.Many2one('account.move', string="Bill")
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
|
||||
def action_create_bil(self):
|
||||
record = {
|
||||
'product_id': self.env.ref('tk_construction_management.construction_product_1').id,
|
||||
'name': self.project_id.name + "\n" + "Name : " + self.name + "\n" + "No : " + self.policy_no,
|
||||
'quantity': 1,
|
||||
'tax_ids': False,
|
||||
'price_unit': self.total_charge
|
||||
}
|
||||
line = [(0, 0, record)]
|
||||
main_data = {
|
||||
'partner_id': self.vendor_id.id,
|
||||
'move_type': 'in_invoice',
|
||||
'invoice_date': fields.date.today(),
|
||||
'invoice_line_ids': line,
|
||||
}
|
||||
bill_id = self.env['account.move'].create(main_data)
|
||||
self.bill_id = bill_id.id
|
||||
|
||||
|
||||
class ConstructionExtraExpense(models.Model):
|
||||
_name = 'extra.expense'
|
||||
_description = "Extra Expense"
|
||||
|
||||
product_id = fields.Many2one(
|
||||
'product.product', string="Expense", domain="[('is_expense','=',True)]")
|
||||
date = fields.Date(string="Date", default=fields.Date.today())
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string="Company", default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
cost = fields.Monetary(string="Cost")
|
||||
bill_id = fields.Many2one('account.move', string="Bill")
|
||||
note = fields.Char(string="Note")
|
||||
project_id = fields.Many2one('tk.construction.project', string="Project")
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_expense_cost(self):
|
||||
for rec in self:
|
||||
rec.cost = rec.product_id.standard_price
|
||||
|
||||
def action_create_expense_bill(self):
|
||||
if self.vendor_id and self.product_id:
|
||||
data = {
|
||||
'product_id': self.product_id.id,
|
||||
'name': (self.product_id.name + " " + self.note) if self.note else self.product_id.name,
|
||||
'quantity': self.qty,
|
||||
'tax_ids': False,
|
||||
'price_unit': self.cost
|
||||
}
|
||||
invoice_line = [(0, 0, data)]
|
||||
invoice_id = self.env['account.move'].create({
|
||||
'partner_id': self.vendor_id.id,
|
||||
'invoice_line_ids': invoice_line,
|
||||
'move_type': 'in_invoice',
|
||||
})
|
||||
self.bill_id = invoice_id.id
|
||||
|
||||
|
||||
class BoqBudget(models.Model):
|
||||
_name = 'boq.budget'
|
||||
_description = "Boq Budget"
|
||||
_rec_name = 'activity_id'
|
||||
|
||||
project_id = fields.Many2one(
|
||||
'tk.construction.project', string="Sub Project")
|
||||
site_id = fields.Many2one(
|
||||
related="project_id.construction_site_id", string="Project")
|
||||
activity_id = fields.Many2one('job.type', string="Work Type")
|
||||
sub_activity_ids = fields.Many2many(
|
||||
related="activity_id.sub_category_ids", string="Sub Activities")
|
||||
sub_activity_id = fields.Many2one('job.sub.category', string="Work Sub Type",
|
||||
domain="[('id','in',sub_activity_ids)]")
|
||||
qty = fields.Float(string="Qty.", default=1.0)
|
||||
total_qty = fields.Float(
|
||||
string="Total Qty.", compute="compute_total_qty", store=True)
|
||||
length = fields.Float(string="Length")
|
||||
width = fields.Float(string="Width")
|
||||
height = fields.Float(string="Height")
|
||||
is_use_measure = fields.Boolean(
|
||||
related="project_id.is_use_measure", store=True)
|
||||
|
||||
@api.depends('project_id.is_use_measure', 'length', 'width', 'height', 'qty')
|
||||
def compute_total_qty(self):
|
||||
for rec in self:
|
||||
total_qty = 0.0
|
||||
if rec.project_id.is_use_measure:
|
||||
total_qty = rec.height * rec.width * rec.length * rec.qty
|
||||
else:
|
||||
total_qty = rec.qty
|
||||
rec.total_qty = total_qty
|
||||
119
addons/tk_construction_management/models/construction_scrap.py
Normal file
119
addons/tk_construction_management/models/construction_scrap.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models, _
|
||||
|
||||
|
||||
class ScrapOrder(models.Model):
|
||||
_name = 'scrap.order'
|
||||
_description = "Construction Scrap Order"
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char(string='Sequence', copy=False, required=True, readonly=True, default=lambda self: _('New'))
|
||||
date = fields.Date(string="Date", default=fields.Date.today())
|
||||
note = fields.Text(string="Note")
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
scrap_order_line_ids = fields.One2many('scrap.order.line', 'scrap_order_id', string="Scrap Order Line")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
total = fields.Monetary(string="Total", compute="_compute_net_total", store=True)
|
||||
invoice_id = fields.Many2one('account.move', string="Invoice")
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
|
||||
@api.depends('scrap_order_line_ids')
|
||||
def _compute_net_total(self):
|
||||
for rec in self:
|
||||
amount = 0.0
|
||||
if rec.scrap_order_line_ids:
|
||||
for data in rec.scrap_order_line_ids:
|
||||
amount = amount + data.net_total
|
||||
rec.total = amount
|
||||
else:
|
||||
rec.total = 0.0
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('name', _('New')) == _('New'):
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('scrap.order') or _('New')
|
||||
res = super(ScrapOrder, self).create(vals_list)
|
||||
return res
|
||||
|
||||
@api.onchange('job_order_id')
|
||||
def _onchange_job_order(self):
|
||||
for rec in self:
|
||||
if not rec.job_order_id:
|
||||
return
|
||||
if rec.job_order_id:
|
||||
lines = []
|
||||
rec.scrap_order_line_ids = [(5, 0, 0)]
|
||||
for data in rec.job_order_id.material_order_ids:
|
||||
lines.append((0, 0, {
|
||||
'product_id': data.material_id.id,
|
||||
'scrap_type': 'material',
|
||||
'qty': 1,
|
||||
}))
|
||||
for data in rec.job_order_id.equipment_order_ids:
|
||||
lines.append((0, 0, {
|
||||
'product_id': data.equipment_id.id,
|
||||
'scrap_type': 'equipment',
|
||||
'qty': 1,
|
||||
}))
|
||||
for data in rec.job_order_id.overhead_order_ids:
|
||||
lines.append((0, 0, {
|
||||
'product_id': data.product_id.id,
|
||||
'scrap_type': 'overhead',
|
||||
'qty': 1,
|
||||
}))
|
||||
rec.scrap_order_line_ids = lines
|
||||
|
||||
def action_create_invoice(self):
|
||||
invoice_line = []
|
||||
for data in self.scrap_order_line_ids:
|
||||
invoice_line.append((0, 0, {
|
||||
'product_id': data.product_id.id,
|
||||
'name': data.product_id.name,
|
||||
'quantity': data.qty,
|
||||
'tax_ids': False,
|
||||
'price_unit': data.dep_cost
|
||||
}))
|
||||
invoice_id = self.env['account.move'].create({
|
||||
'partner_id': self.vendor_id.id,
|
||||
'invoice_line_ids': invoice_line,
|
||||
'move_type': 'out_invoice',
|
||||
})
|
||||
invoice_id.action_post()
|
||||
self.invoice_id = invoice_id.id
|
||||
|
||||
|
||||
class ScrapOrderLine(models.Model):
|
||||
_name = 'scrap.order.line'
|
||||
_description = "Scrap Order Line"
|
||||
|
||||
scrap_type = fields.Selection([('material', 'Material'), ('equipment', 'Equipment'), ('overhead', 'Overhead')],
|
||||
string="Scrap of")
|
||||
product_id = fields.Many2one("product.product", string="Product")
|
||||
qty = fields.Integer(string="Qty.")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
dep_cost = fields.Monetary(string="Value")
|
||||
scrap_order_id = fields.Many2one('scrap.order', string="Scrap Order")
|
||||
net_total = fields.Monetary(string="Total Value", compute="_compute_net_total")
|
||||
|
||||
@api.onchange('scrap_type')
|
||||
def filter_materia_equipment(self):
|
||||
for rec in self:
|
||||
if rec.scrap_type == "material":
|
||||
return {'domain': {'product_id': [('is_material', '=', True)]}}
|
||||
elif rec.scrap_type == "equipment":
|
||||
return {'domain': {'product_id': [('is_equipment', '=', True)]}}
|
||||
elif rec.scrap_type == 'overhead':
|
||||
return {'domain': {'product_id': [('is_overhead', '=', True)]}}
|
||||
|
||||
@api.depends('dep_cost', 'qty')
|
||||
def _compute_net_total(self):
|
||||
for rec in self:
|
||||
if rec.product_id:
|
||||
rec.net_total = rec.dep_cost * rec.qty
|
||||
else:
|
||||
rec.net_total = 0
|
||||
203
addons/tk_construction_management/models/construction_site.py
Normal file
203
addons/tk_construction_management/models/construction_site.py
Normal file
@@ -0,0 +1,203 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ConstructionSite(models.Model):
|
||||
_name = 'tk.construction.site'
|
||||
_description = "Construction Project"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string="Title", tracking=True)
|
||||
start_date = fields.Date(string="Start Date", tracking=True)
|
||||
end_date = fields.Date(string="End Date", tracking=True)
|
||||
status = fields.Selection([('draft', 'Draft'), ('in_progress', 'In Progress'), ('complete', 'Complete')],
|
||||
default='draft', tracking=True)
|
||||
con_project_id = fields.Many2one('tk.construction.project', string="Project", tracking=True)
|
||||
phone = fields.Char(string="Phone")
|
||||
mobile = fields.Char(string="Mobile")
|
||||
email = fields.Char(string="Email")
|
||||
warehouse_id = fields.Many2one('stock.warehouse', string="Warehouse", tracking=True)
|
||||
|
||||
# Address
|
||||
zip = fields.Char(string='Pin Code')
|
||||
street = fields.Char(string='Street1')
|
||||
street2 = fields.Char(string='Street2')
|
||||
city = fields.Char(string='City')
|
||||
country_id = fields.Many2one('res.country', 'Country')
|
||||
state_id = fields.Many2one("res.country.state", string='State', readonly=False, store=True,
|
||||
domain="[('country_id', '=?', country_id)]")
|
||||
longitude = fields.Char(string="Longitude")
|
||||
latitude = fields.Char(string="Latitude")
|
||||
|
||||
# One2Many
|
||||
stakeholder_ids = fields.One2many('stakeholder.line', 'site_id')
|
||||
site_image_ids = fields.One2many('site.images', 'site_id')
|
||||
site_dimension_ids = fields.One2many('site.dimension', 'site_id')
|
||||
document_permit_ids = fields.One2many('document.permit', 'site_id')
|
||||
construction_project_ids = fields.One2many('tk.construction.project', 'construction_site_id')
|
||||
boq_ids = fields.One2many('tk.construction.project', 'construction_site_id')
|
||||
|
||||
# Count & Totals
|
||||
document_count = fields.Integer(string="Document Count", compute="_compute_count")
|
||||
project_count = fields.Integer(string="Project Count", compute="_compute_count")
|
||||
total_area = fields.Float(string="Total Area ", compute="_compute_total_area")
|
||||
|
||||
# Create, Write, Unlink, Constrain
|
||||
@api.constrains('stakeholder_ids')
|
||||
def _check_stakeholder_stack(self):
|
||||
for record in self.stakeholder_ids:
|
||||
duplicates = self.stakeholder_ids.filtered(
|
||||
lambda r: r.id != record.id and r.stakeholder_id.id == record.stakeholder_id.id)
|
||||
if duplicates:
|
||||
raise ValidationError(_("Stakeholder already added !"))
|
||||
|
||||
@api.constrains('stakeholder_ids')
|
||||
def _check_stakeholder_ids(self):
|
||||
percentage = 0.0
|
||||
for record in self.stakeholder_ids:
|
||||
percentage = percentage + record.percentage
|
||||
if percentage > 100:
|
||||
raise ValidationError(_("Percentage cannot exceed the limit of 100%."))
|
||||
|
||||
# Compute
|
||||
def _compute_count(self):
|
||||
for rec in self:
|
||||
rec.document_count = self.env['site.documents'].search_count([('site_id', '=', rec.id)])
|
||||
rec.project_count = self.env['tk.construction.project'].search_count(
|
||||
[('construction_site_id', '=', rec.id)])
|
||||
|
||||
@api.depends('site_dimension_ids')
|
||||
def _compute_total_area(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.site_dimension_ids:
|
||||
for data in rec.site_dimension_ids:
|
||||
total = total + data.area
|
||||
rec.total_area = total
|
||||
|
||||
# Smart Bottom
|
||||
def action_gmap_location(self):
|
||||
if self.longitude and self.latitude:
|
||||
longitude = self.longitude
|
||||
latitude = self.latitude
|
||||
http_url = 'https://maps.google.com/maps?q=loc:' + latitude + ',' + longitude
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'target': 'new',
|
||||
'url': http_url,
|
||||
}
|
||||
else:
|
||||
raise ValidationError(_("! Enter Proper Longitude and Latitude Values"))
|
||||
|
||||
def action_site_document(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Documents'),
|
||||
'res_model': 'site.documents',
|
||||
'domain': [('site_id', '=', self.id)],
|
||||
'context': {'default_site_id': self.id},
|
||||
'view_mode': 'list',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_project(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Project'),
|
||||
'res_model': 'tk.construction.project',
|
||||
'domain': [('construction_site_id', '=', self.id)],
|
||||
'context': {'default_construction_site_id': self.id},
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
# Button
|
||||
def action_site_complete(self):
|
||||
self.status = 'complete'
|
||||
|
||||
def action_site_in_progress(self):
|
||||
self.status = 'in_progress'
|
||||
|
||||
|
||||
# Stakeholder
|
||||
class StakeholderLine(models.Model):
|
||||
_name = 'stakeholder.line'
|
||||
_description = "Stack Holder Line"
|
||||
_rec_name = 'stakeholder_id'
|
||||
|
||||
site_id = fields.Many2one('tk.construction.site', string="Construction Project")
|
||||
stakeholder_id = fields.Many2one('res.partner', domain="[('stack_holder','=',True)]")
|
||||
percentage = fields.Float(string="Percentage")
|
||||
image_1920 = fields.Binary(related="stakeholder_id.image_1920")
|
||||
phone = fields.Char(related="stakeholder_id.phone", string="Phone")
|
||||
email = fields.Char(related="stakeholder_id.email", string="Email")
|
||||
|
||||
|
||||
# Project Documents
|
||||
class SiteDocuments(models.Model):
|
||||
_name = 'site.documents'
|
||||
_description = "Project Documents"
|
||||
_rec_name = 'document_type_id'
|
||||
|
||||
document_type_id = fields.Many2one('site.document.type', string="Document Type")
|
||||
date = fields.Date(string="Date", default=fields.Date.today())
|
||||
site_id = fields.Many2one('tk.construction.site', string="Construction Project", ondelete='cascade')
|
||||
document = fields.Binary(string='Documents', required=True)
|
||||
file_name = fields.Char(string='File Name')
|
||||
|
||||
|
||||
# Project Images
|
||||
class SiteImages(models.Model):
|
||||
_name = 'site.images'
|
||||
_description = "Project Images"
|
||||
|
||||
site_id = fields.Many2one('tk.construction.site', string="Construction Project", ondelete='cascade')
|
||||
name = fields.Char(string='Title')
|
||||
image = fields.Image(string='Images')
|
||||
|
||||
|
||||
# Project Dimension
|
||||
class SiteDimension(models.Model):
|
||||
_name = 'site.dimension'
|
||||
_description = "Project Dimension"
|
||||
_rec_name = "site_id"
|
||||
|
||||
name = fields.Char(string="Title")
|
||||
site_id = fields.Many2one('tk.construction.site', string="Construction Project", ondelete='cascade')
|
||||
length = fields.Float(string="Length(m)")
|
||||
width = fields.Float(string="Width(m)")
|
||||
area = fields.Float(string="Area", compute="_compute_area")
|
||||
|
||||
@api.depends('length', 'width')
|
||||
def _compute_area(self):
|
||||
for rec in self:
|
||||
area = 0.0
|
||||
if rec.length and rec.width:
|
||||
area = rec.length * rec.width
|
||||
rec.area = area
|
||||
|
||||
|
||||
# Document Permit
|
||||
class DocumentPermit(models.Model):
|
||||
_name = 'document.permit'
|
||||
_description = "Document Permit"
|
||||
_rec_name = 'document_type_id'
|
||||
|
||||
document_type_id = fields.Many2one('site.document.type', string="Document Type")
|
||||
date = fields.Date(string="Date", default=fields.Date.today())
|
||||
site_id = fields.Many2one('tk.construction.site', string="Construction Project", ondelete='cascade')
|
||||
document = fields.Binary(string='Documents', required=True)
|
||||
file_name = fields.Char(string='File Name')
|
||||
status = fields.Selection([('a', 'Approve'), ('r', 'Reject')], string="Status")
|
||||
feedback = fields.Char(string="Feedback")
|
||||
submitted_by = fields.Many2one('res.users', string="Submitted by",
|
||||
default=lambda self: self.env.user and self.env.user.id or False)
|
||||
|
||||
def action_approve(self):
|
||||
self.status = 'a'
|
||||
|
||||
def action_reject(self):
|
||||
self.status = 'r'
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models
|
||||
|
||||
|
||||
class ConstructionResUser(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
department_ids = fields.Many2many('construction.department', string="Department ")
|
||||
23
addons/tk_construction_management/models/department.py
Normal file
23
addons/tk_construction_management/models/department.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models
|
||||
|
||||
|
||||
class ConstructionDepartment(models.Model):
|
||||
_name = 'construction.department'
|
||||
_description = "Construction department"
|
||||
|
||||
name = fields.Char(string="Department Name")
|
||||
manager_ids = fields.Many2many('res.users', string="Manager")
|
||||
user_ids = fields.Many2many('res.users', 'construction_user_team_rel', 'user_id', 'team_id_id',
|
||||
string="Responsible", domain="[('id','not in',manager_ids)]")
|
||||
department_id = fields.Many2one('hr.department', string="HR Department")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals):
|
||||
res = super(ConstructionDepartment, self).create(vals)
|
||||
for rec in res:
|
||||
for data in rec.manager_ids:
|
||||
data.department_ids = data.department_ids.ids + [rec.id]
|
||||
return res
|
||||
274
addons/tk_construction_management/models/internal_transfer.py
Normal file
274
addons/tk_construction_management/models/internal_transfer.py
Normal file
@@ -0,0 +1,274 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, models, api, _
|
||||
|
||||
|
||||
class InternalTransfer(models.Model):
|
||||
_name = 'internal.transfer'
|
||||
_description = 'Internal transfer material requisition'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string='Sequence', required=True, readonly=True, default=lambda self: _('New'))
|
||||
title = fields.Char(string="Title")
|
||||
stage = fields.Selection([('draft', 'Draft'), ('in_progress', 'In Progress'), ('done', 'Done'),
|
||||
('cancel', 'Cancel')], default='draft', string="Stage", tracking=True)
|
||||
is_order_created = fields.Boolean()
|
||||
delivery_order_check = fields.Boolean(compute="_compute_delivery_order_status")
|
||||
|
||||
# Other Details
|
||||
date = fields.Date(string="Date", default=fields.Date.today())
|
||||
responsible_id = fields.Many2one('res.users', default=lambda self: self.env.user and self.env.user.id or False,
|
||||
string="Created By")
|
||||
|
||||
# Project Details
|
||||
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company)
|
||||
site_id = fields.Many2one('tk.construction.site', string="Project")
|
||||
project_id = fields.Many2one('tk.construction.project', string="Sub Project")
|
||||
warehouse_id = fields.Many2one(related="site_id.warehouse_id")
|
||||
|
||||
# Work Type & Phase
|
||||
work_type_id = fields.Many2one('job.type', string="Work Type")
|
||||
job_sheet_id = fields.Many2one('job.costing', string="Phase(WBS)")
|
||||
work_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
material_req_id = fields.Many2one('material.requisition', string="Material Requisition")
|
||||
|
||||
# Internal Transfer & Forward Transfer
|
||||
is_forward_transfer = fields.Boolean(tracking=True)
|
||||
forward_transfer_id = fields.Many2one('internal.transfer', string="Forward Transfer")
|
||||
internal_ref = fields.Char(string="Internal Transfer Ref.")
|
||||
|
||||
# Department
|
||||
department_id = fields.Many2one('construction.department', string="Department")
|
||||
manager_ids = fields.Many2many('res.users', string="Manager")
|
||||
user_id = fields.Many2one('res.users', string="Responsible User ")
|
||||
|
||||
# Additional Info
|
||||
vehicle_no = fields.Char(string="Vehicle No")
|
||||
vehicle_name = fields.Char(string="Name")
|
||||
model = fields.Char(string="Model")
|
||||
driver_name = fields.Char(string="Driver Name")
|
||||
phone = fields.Char(string="Phone")
|
||||
|
||||
# One 2 many
|
||||
internal_line_ids = fields.One2many('internal.transfer.line', 'internal_transfer_id',
|
||||
string="Internal Transfer Line")
|
||||
|
||||
# Count
|
||||
delivery_count = fields.Integer(string="Delivery Count", compute="_compute_count")
|
||||
|
||||
# Create, Write, Unlink, Constrain
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('name', _('New')) == _('New'):
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('internal.transfer') or _('New')
|
||||
res = super(InternalTransfer, self).create(vals_list)
|
||||
return res
|
||||
|
||||
def name_get(self):
|
||||
data = []
|
||||
for rec in self:
|
||||
data.append((rec.id, '%s - %s' % (rec.name, rec.title)))
|
||||
return data
|
||||
|
||||
# Compute
|
||||
def _compute_count(self):
|
||||
for rec in self:
|
||||
rec.delivery_count = self.env['stock.picking'].search_count([('transfer_id', '=', rec.id)])
|
||||
|
||||
# Compute
|
||||
@api.depends('internal_line_ids')
|
||||
def _compute_delivery_order_status(self):
|
||||
delivery = True
|
||||
delivery_orders = self.env['stock.picking'].search([('transfer_id', '=', self.id)])
|
||||
if delivery_orders:
|
||||
for rec in delivery_orders:
|
||||
if not rec.state == 'done':
|
||||
delivery = False
|
||||
break
|
||||
else:
|
||||
delivery = False
|
||||
if delivery:
|
||||
self.delivery_order_check = True
|
||||
else:
|
||||
self.delivery_order_check = False
|
||||
|
||||
# Onchange
|
||||
@api.onchange('department_id', 'manager_ids')
|
||||
def _onchange_department_manager(self):
|
||||
ids = []
|
||||
for rec in self:
|
||||
if rec.department_id:
|
||||
ids = rec.department_id.manager_ids.ids
|
||||
return {'domain': {'manager_ids': [('id', 'in', ids)]}}
|
||||
|
||||
@api.onchange('department_id', 'manager_ids', 'user_id')
|
||||
def _onchange_department_responsible(self):
|
||||
ids = []
|
||||
for rec in self:
|
||||
if rec.department_id:
|
||||
ids = rec.department_id.user_ids.ids
|
||||
return {'domain': {'user_id': [('id', 'in', ids)]}}
|
||||
|
||||
# Smart Button
|
||||
def action_view_delivery_order(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Delivery Orders'),
|
||||
'res_model': 'stock.picking',
|
||||
'domain': [('transfer_id', '=', self.id)],
|
||||
'context': {'default_transfer_id': self.id},
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
# Button
|
||||
def action_ready_transfer(self):
|
||||
self.stage = 'in_progress'
|
||||
self.material_req_id.stage = 'internal_transfer'
|
||||
|
||||
def action_complete_transfer(self):
|
||||
self.material_req_id.stage = 'material_arrived'
|
||||
self.work_order_id.state = 'material_arrive'
|
||||
self.stage = 'done'
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Work Order'),
|
||||
'res_model': 'job.order',
|
||||
'res_id': self.work_order_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_cancel_transfer(self):
|
||||
self.material_req_id.stage = 'cancel'
|
||||
self.stage = 'cancel'
|
||||
|
||||
def action_internal_transfer(self):
|
||||
pickup_warehouse_ids = self.internal_line_ids.mapped('pickup_warehouse_id').mapped('id')
|
||||
delivery_warehouse_ids = self.internal_line_ids.mapped('delivery_warehouse_id').mapped('id')
|
||||
for pickup in pickup_warehouse_ids:
|
||||
for delivery in delivery_warehouse_ids:
|
||||
lines = []
|
||||
created_ids = []
|
||||
stock_picking_type_id = False
|
||||
source_id = False
|
||||
destination_id = False
|
||||
for line in self.internal_line_ids:
|
||||
if line.pickup_warehouse_id.id == pickup and line.delivery_warehouse_id.id == delivery:
|
||||
lines.append((0, 0, {
|
||||
'product_id': line.product_id.id,
|
||||
'product_uom_qty': line.qty,
|
||||
'product_uom': line.uom_id.id,
|
||||
'location_id': line.pickup_warehouse_id.lot_stock_id.id,
|
||||
'location_dest_id': line.delivery_warehouse_id.lot_stock_id.id,
|
||||
'name': line.name
|
||||
}))
|
||||
source_id = line.pickup_warehouse_id.lot_stock_id
|
||||
destination_id = line.delivery_warehouse_id.lot_stock_id
|
||||
stock_picking_type_id = self.env['stock.picking.type'].search(
|
||||
[('code', '=', 'internal'), ('warehouse_id', '=', line.pickup_warehouse_id.id)], limit=1)
|
||||
created_ids.append(line.id)
|
||||
if lines and stock_picking_type_id and source_id and destination_id:
|
||||
delivery_record = {
|
||||
'picking_type_id': stock_picking_type_id.id,
|
||||
'location_id': source_id.id,
|
||||
'location_dest_id': destination_id.id,
|
||||
'move_ids_without_package': lines,
|
||||
'transfer_id': self.id,
|
||||
'move_type': 'one'
|
||||
}
|
||||
delivery_id = self.env['stock.picking'].create(delivery_record)
|
||||
delivery_id.action_confirm()
|
||||
for data in created_ids:
|
||||
internal_line_id = self.env['internal.transfer.line'].browse(data)
|
||||
internal_line_id.delivery_order_id = delivery_id.id
|
||||
self.material_req_id.internal_transfer_id = self.id
|
||||
self.is_order_created = True
|
||||
|
||||
def action_forward_transfer(self):
|
||||
is_any_forward = False
|
||||
for data in self.internal_line_ids:
|
||||
if data.delivery_warehouse_id.id == self.warehouse_id.id:
|
||||
is_any_forward = True
|
||||
if not is_any_forward:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "There are no any forward transfer in Internal transfer",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
if is_any_forward:
|
||||
internal_record = {
|
||||
'title': self.title,
|
||||
'site_id': self.site_id.id,
|
||||
'project_id': self.project_id.id,
|
||||
'work_type_id': self.work_type_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id,
|
||||
'work_order_id': self.work_order_id.id,
|
||||
'material_req_id': self.material_req_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
'department_id': self.department_id.id,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'user_id': self.user_id.id,
|
||||
'stage': 'in_progress',
|
||||
'internal_ref': self.name
|
||||
}
|
||||
internal_transfer_id = self.env['internal.transfer'].create(internal_record)
|
||||
for data in self.internal_line_ids:
|
||||
if data.delivery_warehouse_id.id == self.warehouse_id.id:
|
||||
line_record = {
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
'product_id': data.product_id.id,
|
||||
'name': data.name,
|
||||
'qty': data.qty,
|
||||
'pickup_warehouse_id': data.delivery_warehouse_id.id,
|
||||
'delivery_warehouse_id': data.building_id.warehouse_id.id,
|
||||
'internal_transfer_id': internal_transfer_id.id
|
||||
}
|
||||
self.env['internal.transfer.line'].create(line_record)
|
||||
self.forward_transfer_id = internal_transfer_id.id
|
||||
internal_transfer_id.is_forward_transfer = True
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Forward Transfer'),
|
||||
'res_model': 'internal.transfer',
|
||||
'res_id': internal_transfer_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
|
||||
class InternalTransferLine(models.Model):
|
||||
_name = 'internal.transfer.line'
|
||||
_description = "Internal Transfer Line"
|
||||
|
||||
product_id = fields.Many2one('product.product', string="Product")
|
||||
name = fields.Char(string="Description")
|
||||
qty = fields.Integer('Qty.')
|
||||
forcast_qty = fields.Char(string="Forecast Qty.", compute="_compute_forcast_qty")
|
||||
uom_id = fields.Many2one(related="product_id.uom_po_id", string="UOM")
|
||||
pickup_warehouse_id = fields.Many2one('stock.warehouse', string="Pickup Warehouse")
|
||||
delivery_warehouse_id = fields.Many2one('stock.warehouse', string="Delivery Warehouse")
|
||||
internal_transfer_id = fields.Many2one('internal.transfer', string="Internal Transfer")
|
||||
delivery_order_id = fields.Many2one('stock.picking', string="Do")
|
||||
state = fields.Selection(related="delivery_order_id.state", string="Status")
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
|
||||
@api.depends('pickup_warehouse_id', 'product_id')
|
||||
def _compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
qty = 0.0
|
||||
if rec.product_id:
|
||||
qty = rec.product_id.with_context(warehouse=rec.pickup_warehouse_id.id).virtual_available
|
||||
rec.forcast_qty = qty
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
553
addons/tk_construction_management/models/job_costing.py
Normal file
553
addons/tk_construction_management/models/job_costing.py
Normal file
@@ -0,0 +1,553 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class JobCosting(models.Model):
|
||||
_name = 'job.costing'
|
||||
_description = "Job Costing"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string='Sequence', required=True, readonly=True, default=lambda self: _('New'))
|
||||
title = fields.Char(string="Title")
|
||||
create_date = fields.Date(string="Start Date", default=fields.Date.today())
|
||||
close_date = fields.Date(string="End Date")
|
||||
status = fields.Selection([('draft', 'Draft'), ('waiting_approval', 'Waiting Approval'), ('approved', 'Approved'),
|
||||
('in_progress', 'In Progress'), ('complete', 'Complete'), ('cancel', 'Cancel'),
|
||||
('reject', 'Reject')], default='draft', tracking=True)
|
||||
|
||||
responsible_id = fields.Many2one('res.users', default=lambda self: self.env.user and self.env.user.id or False,
|
||||
string="Created By")
|
||||
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
site_id = fields.Many2one('tk.construction.site', string="Project")
|
||||
project_id = fields.Many2one('tk.construction.project', string="Sub Project")
|
||||
activity_id = fields.Many2one('job.type', string="Work Type")
|
||||
|
||||
# Department
|
||||
department_id = fields.Many2one('construction.department', string="Department")
|
||||
manager_ids = fields.Many2many('res.users', string="Manager")
|
||||
user_id = fields.Many2one('res.users', string="Responsible")
|
||||
|
||||
# One2 many
|
||||
work_order_ids = fields.One2many('job.order', 'job_sheet_id')
|
||||
cost_material_ids = fields.One2many('cost.material.line', 'job_costing_id')
|
||||
cost_equipment_ids = fields.One2many('cost.equipment.line', 'job_costing_id')
|
||||
cost_labour_ids = fields.One2many('cost.labour.line', 'job_costing_id')
|
||||
cost_overhead_ids = fields.One2many('cost.overhead.line', 'job_costing_id')
|
||||
|
||||
# Total
|
||||
material_total_cost = fields.Monetary(string="Total Material Cost", compute="_compute_total")
|
||||
equipment_total_cost = fields.Monetary(string="Total Equipment Cost", compute="_compute_total")
|
||||
labour_total_cost = fields.Monetary(string="Total Labour Cost", compute="_compute_total")
|
||||
overhead_total_cost = fields.Monetary(string="Total Overhead Cost", compute="_compute_total")
|
||||
material_actual_cost = fields.Monetary(string="Actual Material Cost", compute="_compute_total")
|
||||
equipment_actual_cost = fields.Monetary(string="Actual Equipment Cost", compute="_compute_total")
|
||||
labour_actual_cost = fields.Monetary(string="Actual Labour Cost", compute="_compute_total")
|
||||
overhead_actual_cost = fields.Monetary(string="Actual Overhead Cost", compute="_compute_total")
|
||||
|
||||
# Count
|
||||
job_order_count = fields.Integer(string="Jon Order", compute="_compute_count")
|
||||
mrq_count = fields.Integer(string="MRQ Order", compute="_compute_count")
|
||||
|
||||
# Budget Qty.
|
||||
sub_work_type_ids = fields.Many2many('job.sub.category')
|
||||
|
||||
# Create, Write, Unlink, Constrain
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('name', _('New')) == _('New') and vals.get('project_id'):
|
||||
project_id = self.env['tk.construction.project'].browse(vals.get('project_id'))
|
||||
vals['name'] = str(project_id.code) + "/" + (
|
||||
self.env['ir.sequence'].next_by_code('job.costing') or _('New'))
|
||||
res = super(JobCosting, self).create(vals_list)
|
||||
return res
|
||||
|
||||
def name_get(self):
|
||||
data = []
|
||||
for rec in self:
|
||||
data.append((rec.id, '%s - %s' % (rec.name, rec.title)))
|
||||
return data
|
||||
|
||||
# Onchange
|
||||
@api.onchange('project_id')
|
||||
def _onchange_project_info(self):
|
||||
for rec in self:
|
||||
rec.project_id = rec.project_id.id
|
||||
rec.department_id = rec.project_id.department_id.id
|
||||
rec.manager_ids = rec.project_id.manager_ids.ids
|
||||
rec.user_id = rec.project_id.user_id.id
|
||||
|
||||
# Compute
|
||||
@api.depends('cost_material_ids', 'cost_equipment_ids', 'cost_labour_ids', 'cost_overhead_ids')
|
||||
def _compute_total(self):
|
||||
material, equipment, labour, overhead = 0.0, 0.0, 0.0, 0.0
|
||||
material_actual_cost = self.env['order.material.line'].search(
|
||||
[('job_sheet_id', '=', self.id), ('state', '=', 'complete')]).mapped(
|
||||
'total_price')
|
||||
equipment_actual_cost = self.env['order.equipment.line'].search(
|
||||
[('job_sheet_id', '=', self.id), ('state', '=', 'complete')]).mapped(
|
||||
'total_cost')
|
||||
labour_actual_cost = self.env['order.labour.line'].search(
|
||||
[('job_sheet_id', '=', self.id), ('state', '=', 'complete')]).mapped(
|
||||
'sub_total')
|
||||
overhead_actual_cost = self.env['order.overhead.line'].search(
|
||||
[('job_sheet_id', '=', self.id), ('state', '=', 'complete')]).mapped(
|
||||
'sub_total')
|
||||
for rec in self:
|
||||
for data in rec.cost_material_ids:
|
||||
material = material + data.total_cost
|
||||
for data in rec.cost_equipment_ids:
|
||||
equipment = equipment + data.total_cost
|
||||
for data in rec.cost_labour_ids:
|
||||
labour = labour + data.sub_total
|
||||
for data in rec.cost_overhead_ids:
|
||||
overhead = overhead + data.sub_total
|
||||
rec.material_total_cost = material
|
||||
rec.equipment_total_cost = equipment
|
||||
rec.labour_total_cost = labour
|
||||
rec.overhead_total_cost = overhead
|
||||
rec.material_actual_cost = sum(material_actual_cost)
|
||||
rec.equipment_actual_cost = sum(equipment_actual_cost)
|
||||
rec.labour_actual_cost = sum(labour_actual_cost)
|
||||
rec.overhead_actual_cost = sum(overhead_actual_cost)
|
||||
|
||||
def _compute_count(self):
|
||||
for rec in self:
|
||||
rec.job_order_count = self.env['job.order'].search_count([('job_sheet_id', '=', rec.id)])
|
||||
rec.mrq_count = self.env['material.requisition'].search_count([('job_sheet_id', '=', rec.id)])
|
||||
|
||||
# Smart Button
|
||||
def action_view_job_order(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Work Order'),
|
||||
'res_model': 'job.order',
|
||||
'domain': [('job_sheet_id', '=', self.id)],
|
||||
'context': {'create': False},
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_mrq(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Material Requisition'),
|
||||
'res_model': 'material.requisition',
|
||||
'domain': [('job_sheet_id', '=', self.id)],
|
||||
'context': {'default_job_sheet_id': self.id},
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_department_approval(self):
|
||||
self.status = 'waiting_approval'
|
||||
|
||||
def action_approve_phase(self):
|
||||
self.status = 'approved'
|
||||
|
||||
def action_reject_phase(self):
|
||||
self.status = 'reject'
|
||||
|
||||
def action_in_progress(self):
|
||||
self.status = 'in_progress'
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.status = 'draft'
|
||||
|
||||
def action_cancel_phase(self):
|
||||
self.status = 'cancel'
|
||||
|
||||
def action_complete_phase(self):
|
||||
is_complete_work_order = True
|
||||
for data in self.work_order_ids:
|
||||
if data.state != 'complete':
|
||||
is_complete_work_order = False
|
||||
break
|
||||
if not is_complete_work_order:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "Please complete all work orders related to this phase.",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
|
||||
if is_complete_work_order:
|
||||
self.status = 'complete'
|
||||
|
||||
def action_create_work_order(self):
|
||||
material_line = []
|
||||
equipment_line = []
|
||||
labour_line = []
|
||||
overhead_line = []
|
||||
ctx = {
|
||||
'default_site_id': self.site_id.id,
|
||||
'default_project_id': self.project_id.id,
|
||||
'default_start_date': self.create_date,
|
||||
'default_end_date': self.close_date,
|
||||
'default_work_type_id': self.activity_id.id,
|
||||
'default_job_sheet_id': self.id,
|
||||
'default_department_id': self.department_id.id,
|
||||
'default_manager_ids': self.manager_ids.ids,
|
||||
'default_user_id': self.user_id.id
|
||||
}
|
||||
for data in self.cost_material_ids:
|
||||
material_line.append((0, 0, {
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
'material_id': data.material_id.id,
|
||||
'name': data.name,
|
||||
'qty': data.forcast_qty,
|
||||
'remain_qty': data.forcast_qty,
|
||||
'phase_forcast_qty': data.forcast_qty,
|
||||
'tax_id': data.tax_id.id,
|
||||
'price': data.cost
|
||||
}))
|
||||
for data in self.cost_equipment_ids:
|
||||
equipment_line.append((0, 0, {
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
'equipment_id': data.equipment_id.id,
|
||||
'cost_type': data.cost_type,
|
||||
'desc': data.name,
|
||||
'qty': data.forcast_qty,
|
||||
'cost': data.cost,
|
||||
'phase_forcast_qty': data.forcast_qty,
|
||||
'tax_id': data.tax_id.id,
|
||||
}))
|
||||
for data in self.cost_labour_ids:
|
||||
labour_line.append((0, 0, {
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
'product_id': data.product_id.id,
|
||||
'name': data.name,
|
||||
'hours': data.forcast_qty,
|
||||
'cost': data.cost,
|
||||
'phase_forcast_qty': data.forcast_qty,
|
||||
'tax_id': data.tax_id.id,
|
||||
}))
|
||||
for data in self.cost_overhead_ids:
|
||||
overhead_line.append((0, 0, {
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
'product_id': data.product_id.id,
|
||||
'name': data.name,
|
||||
'qty': data.forcast_qty,
|
||||
'cost': data.cost,
|
||||
'phase_forcast_qty': data.forcast_qty,
|
||||
'tax_id': data.tax_id.id,
|
||||
}))
|
||||
ctx['default_material_order_ids'] = material_line
|
||||
ctx['default_equipment_order_ids'] = equipment_line
|
||||
ctx['default_labour_order_ids'] = labour_line
|
||||
ctx['default_overhead_order_ids'] = overhead_line
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Work Order'),
|
||||
'res_model': 'job.order',
|
||||
'context': ctx,
|
||||
'view_mode': 'form',
|
||||
'target': 'new'
|
||||
}
|
||||
|
||||
|
||||
class CostMaterialLine(models.Model):
|
||||
_name = 'cost.material.line'
|
||||
_description = 'Construction Job Cost Material Line'
|
||||
|
||||
material_id = fields.Many2one('product.product', string="Material", domain="[('is_material','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
cost = fields.Float(string="Cost")
|
||||
total_cost = fields.Monetary(string="Total Cost", compute="_compute_total_cost", store=True)
|
||||
uom_id = fields.Many2one(related="material_id.uom_po_id", string="Unit of Measure")
|
||||
job_costing_id = fields.Many2one('job.costing', string="Job Costing")
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
budget_qty = fields.Float(string="Budget Qty.", compute="compute_budget_qty", store=True)
|
||||
boq_per_qty = fields.Float(string="Per BOQ RA. Qty.")
|
||||
total_remain_boq_qty = fields.Float(string="Total Remain BOQ Qty")
|
||||
forcast_qty = fields.Float(string="Forcast Qty.", compute="compute_forcast_qty")
|
||||
used_qty = fields.Float(string="Used Qty.")
|
||||
used_budget_qty = fields.Float(string="Used Budget Qty.")
|
||||
remain_qty = fields.Float(string="Remain Qty.", compute="compute_remain_qty")
|
||||
|
||||
@api.onchange('material_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.material_id.name
|
||||
rec.cost = rec.material_id.standard_price
|
||||
|
||||
@api.depends('material_id', 'qty', 'cost', 'tax_id')
|
||||
def _compute_total_cost(self):
|
||||
for rec in self:
|
||||
total_cost = 0.0
|
||||
tax_amount = 0.0
|
||||
if rec.material_id:
|
||||
total_cost = rec.qty * rec.cost
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * (rec.qty * rec.cost) / 100
|
||||
total_cost = tax_amount + total_cost
|
||||
rec.total_cost = total_cost
|
||||
|
||||
@api.depends('boq_per_qty', 'qty')
|
||||
def compute_budget_qty(self):
|
||||
for rec in self:
|
||||
budget_qty = 0
|
||||
if rec.boq_per_qty > 0:
|
||||
budget_qty = rec.qty / rec.boq_per_qty
|
||||
rec.budget_qty = budget_qty
|
||||
|
||||
@api.depends('qty',
|
||||
'job_costing_id.activity_id',
|
||||
'material_id',
|
||||
'sub_category_id',
|
||||
'job_costing_id.work_order_ids.material_order_ids.qty')
|
||||
def compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
forcast_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
for data in record.material_order_ids:
|
||||
if data.material_id.id == rec.material_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
forcast_qty = forcast_qty + data.qty
|
||||
rec.forcast_qty = rec.qty - forcast_qty
|
||||
|
||||
@api.depends('job_costing_id.work_order_ids.material_order_ids.qty', 'job_costing_id.work_order_ids.state', 'qty')
|
||||
def compute_remain_qty(self):
|
||||
for rec in self:
|
||||
remain_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
if record.state == 'complete':
|
||||
for data in record.material_order_ids:
|
||||
if data.material_id.id == rec.material_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
remain_qty = remain_qty + data.qty
|
||||
rec.remain_qty = rec.qty - remain_qty
|
||||
|
||||
|
||||
class CostEquipmentLine(models.Model):
|
||||
_name = 'cost.equipment.line'
|
||||
_description = 'Construction Job Cost Equipment Line'
|
||||
|
||||
equipment_id = fields.Many2one('product.product', string="Equipment", domain="[('is_equipment','=',True)]")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
cost_type = fields.Selection(
|
||||
[('depreciation_cost', 'Depreciation Cost'), ('investment_cost', 'Investment/Interest Cost'),
|
||||
('tax', 'Tax'), ('rent', 'Rent'), ('other', 'Other')], string="Type", default='rent')
|
||||
name = fields.Char(string='Description')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
cost = fields.Monetary(string="Estimation Cost")
|
||||
total_cost = fields.Monetary(string="Total Cost", compute="_compute_total_cost", store=True)
|
||||
job_costing_id = fields.Many2one('job.costing', string="Job Costing")
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
budget_qty = fields.Float(string="Budget Qty.", compute="compute_budget_qty", store=True)
|
||||
boq_per_qty = fields.Float(string="Per BOQ RA. Qty.")
|
||||
total_remain_boq_qty = fields.Float(string="Total Remain BOQ Qty")
|
||||
forcast_qty = fields.Float(string="Forcast Qty.", compute="compute_forcast_qty")
|
||||
used_qty = fields.Float(string="Used Qty.")
|
||||
used_budget_qty = fields.Float(string="Used Budget Qty.")
|
||||
remain_qty = fields.Float(string="Remain Qty.", compute="compute_remain_qty")
|
||||
|
||||
@api.depends('equipment_id', 'qty', 'cost', 'tax_id')
|
||||
def _compute_total_cost(self):
|
||||
for rec in self:
|
||||
total_cost = 0.0
|
||||
if rec.equipment_id:
|
||||
total_cost = rec.qty * rec.cost
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * (rec.qty * rec.cost) / 100
|
||||
total_cost = tax_amount + total_cost
|
||||
rec.total_cost = total_cost
|
||||
|
||||
@api.onchange('equipment_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.equipment_id.name
|
||||
rec.cost = rec.equipment_id.standard_price
|
||||
|
||||
@api.depends('boq_per_qty', 'qty')
|
||||
def compute_budget_qty(self):
|
||||
for rec in self:
|
||||
budget_qty = 0
|
||||
if rec.boq_per_qty > 0:
|
||||
budget_qty = rec.qty / rec.boq_per_qty
|
||||
rec.budget_qty = budget_qty
|
||||
|
||||
@api.depends('qty',
|
||||
'job_costing_id.activity_id',
|
||||
'equipment_id',
|
||||
'sub_category_id',
|
||||
'job_costing_id.work_order_ids.equipment_order_ids.qty')
|
||||
def compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
forcast_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
for data in record.equipment_order_ids:
|
||||
if data.equipment_id.id == rec.equipment_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
forcast_qty = forcast_qty + data.qty
|
||||
rec.forcast_qty = rec.qty - forcast_qty
|
||||
|
||||
@api.depends('job_costing_id.work_order_ids.equipment_order_ids.qty', 'job_costing_id.work_order_ids.state', 'qty')
|
||||
def compute_remain_qty(self):
|
||||
for rec in self:
|
||||
remain_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
if record.state == 'complete':
|
||||
for data in record.equipment_order_ids:
|
||||
if data.equipment_id.id == rec.equipment_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
remain_qty = remain_qty + data.qty
|
||||
rec.remain_qty = rec.qty - remain_qty
|
||||
|
||||
|
||||
class CostLabourLine(models.Model):
|
||||
_name = 'cost.labour.line'
|
||||
_description = "Cost Labour Line"
|
||||
|
||||
job_costing_id = fields.Many2one('job.costing', string="Job Costing")
|
||||
product_id = fields.Many2one('product.product', string="Product", domain="[('is_labour','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
hours = fields.Float(string="Hours")
|
||||
remain_hours = fields.Float(string="Remaining Hours")
|
||||
cost = fields.Monetary(string="Cost / Hour")
|
||||
sub_total = fields.Monetary(string="Sub Total", compute="_compute_total_cost", store=True)
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
budget_qty = fields.Float(string="Budget Qty.", compute="compute_budget_qty", store=True)
|
||||
boq_per_qty = fields.Float(string="Per BOQ RA. Qty.")
|
||||
total_remain_boq_qty = fields.Float(string="Total Remain BOQ Qty")
|
||||
forcast_qty = fields.Float(string="Forcast Hours", compute="compute_forcast_qty")
|
||||
used_qty = fields.Float(string="Used Qty.")
|
||||
used_budget_qty = fields.Float(string="Used Budget Qty.")
|
||||
remain_qty = fields.Float(string="Remain Hours", compute="compute_remain_qty")
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
rec.cost = rec.product_id.standard_price
|
||||
|
||||
@api.depends('product_id', 'hours', 'cost', 'tax_id')
|
||||
def _compute_total_cost(self):
|
||||
for rec in self:
|
||||
total_cost = 0.0
|
||||
if rec.product_id:
|
||||
total_cost = rec.hours * rec.cost
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * (rec.hours * rec.cost) / 100
|
||||
total_cost = tax_amount + total_cost
|
||||
rec.sub_total = total_cost
|
||||
|
||||
@api.depends('boq_per_qty', 'hours')
|
||||
def compute_budget_qty(self):
|
||||
for rec in self:
|
||||
budget_qty = 0
|
||||
if rec.boq_per_qty > 0:
|
||||
budget_qty = rec.hours / rec.boq_per_qty
|
||||
rec.budget_qty = budget_qty
|
||||
|
||||
@api.depends('hours',
|
||||
'job_costing_id.activity_id',
|
||||
'product_id',
|
||||
'sub_category_id',
|
||||
'job_costing_id.work_order_ids.labour_order_ids.hours')
|
||||
def compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
forcast_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
for data in record.labour_order_ids:
|
||||
if data.product_id.id == rec.product_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
forcast_qty = forcast_qty + data.hours
|
||||
rec.forcast_qty = rec.hours - forcast_qty
|
||||
|
||||
@api.depends('job_costing_id.work_order_ids.labour_order_ids.hours', 'job_costing_id.work_order_ids.state', 'hours')
|
||||
def compute_remain_qty(self):
|
||||
for rec in self:
|
||||
remain_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
if record.state == 'complete':
|
||||
for data in record.labour_order_ids:
|
||||
if data.product_id.id == rec.product_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
remain_qty = remain_qty + data.hours
|
||||
rec.remain_qty = rec.hours - remain_qty
|
||||
|
||||
|
||||
class CostOverheadLine(models.Model):
|
||||
_name = 'cost.overhead.line'
|
||||
_description = "Cost Overhead Line"
|
||||
|
||||
job_costing_id = fields.Many2one('job.costing', string="Job Costing")
|
||||
product_id = fields.Many2one('product.product', string="Product", domain="[('is_overhead','=',True)]")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
name = fields.Char(string="Description")
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
uom_id = fields.Many2one(related="product_id.uom_po_id", string="UOM")
|
||||
cost = fields.Monetary(string="Cost / Unit")
|
||||
sub_total = fields.Monetary(string="Sub Total", compute="_compute_total_cost", store=True)
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
budget_qty = fields.Float(string="Budget Qty.", compute="compute_budget_qty", store=True)
|
||||
boq_per_qty = fields.Float(string="Per BOQ RA. Qty.")
|
||||
total_remain_boq_qty = fields.Float(string="Total Remain BOQ Qty")
|
||||
forcast_qty = fields.Float(string="Forcast Qty.", compute="compute_forcast_qty")
|
||||
used_qty = fields.Float(string="Used Qty.")
|
||||
used_budget_qty = fields.Float(string="Used Budget Qty.")
|
||||
remain_qty = fields.Float(string="Remain Qty.", compute="compute_remain_qty")
|
||||
|
||||
@api.depends('product_id', 'qty', 'cost', 'tax_id')
|
||||
def _compute_total_cost(self):
|
||||
for rec in self:
|
||||
total_cost = 0.0
|
||||
if rec.product_id:
|
||||
total_cost = rec.qty * rec.cost
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * (rec.qty * rec.cost) / 100
|
||||
total_cost = tax_amount + total_cost
|
||||
rec.sub_total = total_cost
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
rec.cost = rec.product_id.standard_price
|
||||
|
||||
@api.depends('boq_per_qty', 'qty')
|
||||
def compute_budget_qty(self):
|
||||
for rec in self:
|
||||
budget_qty = 0
|
||||
if rec.boq_per_qty > 0:
|
||||
budget_qty = rec.qty / rec.boq_per_qty
|
||||
rec.budget_qty = budget_qty
|
||||
|
||||
@api.depends('qty',
|
||||
'job_costing_id.activity_id',
|
||||
'product_id',
|
||||
'sub_category_id',
|
||||
'job_costing_id.work_order_ids.overhead_order_ids.qty')
|
||||
def compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
forcast_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
for data in record.overhead_order_ids:
|
||||
if data.product_id.id == rec.product_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
forcast_qty = forcast_qty + data.qty
|
||||
rec.forcast_qty = rec.qty - forcast_qty
|
||||
|
||||
@api.depends('job_costing_id.work_order_ids.overhead_order_ids.qty', 'job_costing_id.work_order_ids.state', 'qty')
|
||||
def compute_remain_qty(self):
|
||||
for rec in self:
|
||||
remain_qty = 0.0
|
||||
for record in rec.job_costing_id.work_order_ids:
|
||||
if record.state == 'complete':
|
||||
for data in record.overhead_order_ids:
|
||||
if data.product_id.id == rec.product_id.id and data.sub_category_id.id == rec.sub_category_id.id:
|
||||
remain_qty = remain_qty + data.qty
|
||||
rec.remain_qty = rec.qty - remain_qty
|
||||
748
addons/tk_construction_management/models/job_order.py
Normal file
748
addons/tk_construction_management/models/job_order.py
Normal file
@@ -0,0 +1,748 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models, _
|
||||
|
||||
|
||||
class JobOrder(models.Model):
|
||||
_name = 'job.order'
|
||||
_description = "Work Order"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string='Sequence', required=True,
|
||||
readonly=True, default=lambda self: _('New'))
|
||||
title = fields.Char(string="Title")
|
||||
stage = fields.Selection([('draft', 'Draft'),
|
||||
('waiting_approval', 'Waiting for approval'),
|
||||
('approved', 'Approved'),
|
||||
('complete', 'Complete'),
|
||||
('cancel', 'Cancel')], default='draft', tracking=True)
|
||||
is_user = fields.Boolean(compute="_compute_user_role")
|
||||
is_material_requisition = fields.Boolean(compute="compute_material_req")
|
||||
|
||||
# Project Details
|
||||
site_id = fields.Many2one('tk.construction.site', string="Project")
|
||||
project_id = fields.Many2one(
|
||||
'tk.construction.project', string="Sub Project")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string="Company", default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
warehouse_id = fields.Many2one(
|
||||
related="project_id.warehouse_id", string="Warehouse")
|
||||
|
||||
# Other Details
|
||||
responsible_id = fields.Many2one('res.users', default=lambda self: self.env.user and self.env.user.id or False,
|
||||
string="Created By")
|
||||
start_date = fields.Date(string="Start Date")
|
||||
end_date = fields.Date(string="End Date")
|
||||
|
||||
# Work Type
|
||||
work_type_id = fields.Many2one('job.type', string="Work Type")
|
||||
job_sheet_id = fields.Many2one(
|
||||
'job.costing', string="Project Phase(WBS)", domain="[('project_id','=',project_id)]")
|
||||
|
||||
# Department
|
||||
department_id = fields.Many2one(
|
||||
'construction.department', string="Department")
|
||||
manager_ids = fields.Many2many('res.users', string="Manager")
|
||||
user_id = fields.Many2one('res.users', string="Responsible")
|
||||
|
||||
# Material
|
||||
material_req_id = fields.Many2one(
|
||||
'material.requisition', string="Material Request")
|
||||
state = fields.Selection([('draft', 'Draft'),
|
||||
('material_request', 'Material Request'),
|
||||
('material_arrive', 'Material Arrived'),
|
||||
('in_progress', 'In Progress'),
|
||||
('complete', 'Complete'),
|
||||
('cancel', 'Cancel')], default='draft')
|
||||
|
||||
# Task Details
|
||||
project_project_id = fields.Many2one(
|
||||
related="project_id.project_id", string="Project ")
|
||||
task_name = fields.Char(string="Task Title")
|
||||
task_desc = fields.Text(string="Description")
|
||||
assignees_ids = fields.Many2many('res.users', 'construction_assign_dep_rel', 'assign_id', 'dep_id',
|
||||
string="Assignees")
|
||||
task_id = fields.Many2one('project.task', string="Task")
|
||||
|
||||
# One2 many
|
||||
material_order_ids = fields.One2many('order.material.line', 'job_order_id')
|
||||
equipment_order_ids = fields.One2many(
|
||||
'order.equipment.line', 'job_order_id')
|
||||
labour_order_ids = fields.One2many('order.labour.line', 'job_order_id')
|
||||
overhead_order_ids = fields.One2many('order.overhead.line', 'job_order_id')
|
||||
|
||||
# Total
|
||||
equipment_total_cost = fields.Monetary(
|
||||
string="Equipment Cost", compute="_compute_total")
|
||||
labour_total_cost = fields.Monetary(
|
||||
string="Labour Cost", compute="_compute_total")
|
||||
overhead_total_cost = fields.Monetary(
|
||||
string="Overhead Cost", compute="_compute_total")
|
||||
|
||||
# Consume Order and Subcontract
|
||||
consume_order_ids = fields.One2many('material.consume', 'job_order_id')
|
||||
equipment_contract_ids = fields.One2many(
|
||||
'equipment.subcontract', 'job_order_id')
|
||||
labour_contract_ids = fields.One2many('labour.subcontract', 'job_order_id')
|
||||
overhead_contract_ids = fields.One2many(
|
||||
'overhead.subcontract', 'job_order_id')
|
||||
|
||||
# Count
|
||||
po_count = fields.Integer(string="Purchase Count",
|
||||
compute="_compute_count")
|
||||
equip_po_count = fields.Integer(
|
||||
string="Purchase Equip Count", compute="_compute_count")
|
||||
labour_po_count = fields.Integer(
|
||||
string="Purchase Labour Count", compute="_compute_count")
|
||||
overhead_po_count = fields.Integer(
|
||||
string="Purchase Overhead Count", compute="_compute_count")
|
||||
bill_count = fields.Integer(string="Bill Count", compute="_compute_count")
|
||||
delivery_count = fields.Integer(
|
||||
string="Delivery Count", compute="_compute_count")
|
||||
equip_contract_count = fields.Integer(
|
||||
string="Equipment Contract Count", compute="_compute_count")
|
||||
labour_contract_count = fields.Integer(
|
||||
string="Labour Contract Count", compute="_compute_count")
|
||||
overhead_contract_count = fields.Integer(
|
||||
string="Overhead Contract Count", compute="_compute_count")
|
||||
material_consume_count = fields.Integer(
|
||||
string="Material Consume Count", compute="_compute_count")
|
||||
|
||||
# Create, Write, Unlink, Constrain
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('name', _('New')) == _('New') and vals.get('project_id'):
|
||||
project_id = self.env['tk.construction.project'].browse(
|
||||
vals.get('project_id'))
|
||||
vals['name'] = str(project_id.code) + "/" + (
|
||||
self.env['ir.sequence'].next_by_code('job.order') or _('New'))
|
||||
res = super(JobOrder, self).create(vals_list)
|
||||
for rec in res:
|
||||
data = {
|
||||
'name': rec.task_name,
|
||||
'description': rec.task_desc,
|
||||
'user_ids': rec.assignees_ids.ids,
|
||||
'project_id': rec.project_project_id.id,
|
||||
'job_order_id': rec.id,
|
||||
'con_project_id': rec.project_id.id,
|
||||
'date_deadline': rec.end_date
|
||||
}
|
||||
task_id = self.env['project.task'].create(data)
|
||||
rec.task_id = task_id.id
|
||||
return res
|
||||
|
||||
def name_get(self):
|
||||
data = []
|
||||
for rec in self:
|
||||
data.append((rec.id, '%s - %s' % (rec.name, rec.title)))
|
||||
return data
|
||||
|
||||
# Compute
|
||||
def _compute_count(self):
|
||||
for rec in self:
|
||||
rec.po_count = self.env['purchase.order'].search_count(
|
||||
[('job_order_id', '=', rec.id)])
|
||||
rec.equip_po_count = self.env['purchase.order'].search_count(
|
||||
[('job_order_id', '=', rec.id), ('purchase_order', '=', 'equipment')])
|
||||
rec.labour_po_count = self.env['purchase.order'].search_count(
|
||||
[('job_order_id', '=', rec.id), ('purchase_order', '=', 'labour')])
|
||||
rec.overhead_po_count = self.env['purchase.order'].search_count(
|
||||
[('job_order_id', '=', rec.id), ('purchase_order', '=', 'overhead')])
|
||||
rec.bill_count = self.env['account.move'].search_count(
|
||||
[('job_order_id', '=', rec.id)])
|
||||
rec.delivery_count = self.env['stock.picking'].search_count(
|
||||
[('consume_order_id', '=', rec.id)])
|
||||
rec.equip_contract_count = self.env['equipment.subcontract'].search_count(
|
||||
[('job_order_id', '=', rec.id)])
|
||||
rec.labour_contract_count = self.env['labour.subcontract'].search_count(
|
||||
[('job_order_id', '=', rec.id)])
|
||||
rec.overhead_contract_count = self.env['overhead.subcontract'].search_count(
|
||||
[('job_order_id', '=', rec.id)])
|
||||
rec.material_consume_count = self.env['material.consume'].search_count(
|
||||
[('job_order_id', '=', rec.id)])
|
||||
|
||||
def _compute_user_role(self):
|
||||
if self.env.user.has_group('tk_construction_management.advance_construction_user'):
|
||||
self.is_user = True
|
||||
else:
|
||||
self.is_user = False
|
||||
|
||||
@api.depends('material_order_ids')
|
||||
def compute_material_req(self):
|
||||
for rec in self:
|
||||
material_req = False
|
||||
for data in rec.material_order_ids:
|
||||
if data.qty != 0:
|
||||
material_req = True
|
||||
break
|
||||
rec.is_material_requisition = material_req
|
||||
|
||||
@api.depends('material_order_ids', 'equipment_order_ids', 'labour_order_ids', 'overhead_order_ids')
|
||||
def _compute_total(self):
|
||||
equipment, labour, overhead = 0, 0, 0
|
||||
for rec in self:
|
||||
for data in rec.equipment_order_ids:
|
||||
equipment = equipment + data.total_cost
|
||||
for data in rec.labour_order_ids:
|
||||
labour = labour + data.sub_total
|
||||
for data in rec.overhead_order_ids:
|
||||
overhead = overhead + data.sub_total
|
||||
rec.equipment_total_cost = equipment
|
||||
rec.labour_total_cost = labour
|
||||
rec.overhead_total_cost = overhead
|
||||
|
||||
# Smart Button
|
||||
def action_view_purchase_order(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Purchase Order'),
|
||||
'res_model': 'purchase.order',
|
||||
'domain': [('job_order_id', '=', self.id)],
|
||||
'context': {
|
||||
'default_job_order_id': self.id,
|
||||
'group_by': 'purchase_order'
|
||||
},
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_bills(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Bills'),
|
||||
'res_model': 'account.move',
|
||||
'domain': [('job_order_id', '=', self.id)],
|
||||
'context': {
|
||||
'default_job_order_id': self.id
|
||||
},
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_delivery_order(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Delivery Order'),
|
||||
'res_model': 'stock.picking',
|
||||
'domain': [('consume_order_id', '=', self.id)],
|
||||
'context': {
|
||||
'default_': self.id
|
||||
},
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_purchase_order_equipment(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Equipment PO'),
|
||||
'res_model': 'purchase.order',
|
||||
'domain': [('job_order_id', '=', self.id), ('purchase_order', '=', 'equipment')],
|
||||
'context': {
|
||||
'default_job_order_id': self.id,
|
||||
'purchase_order': 'equipment'
|
||||
},
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_contract_equipment(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Equipment Contract'),
|
||||
'res_model': 'equipment.subcontract',
|
||||
'domain': [('job_order_id', '=', self.id)],
|
||||
'context': {
|
||||
'create': False,
|
||||
},
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_contract_labour(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Labour Contract'),
|
||||
'res_model': 'labour.subcontract',
|
||||
'domain': [('job_order_id', '=', self.id)],
|
||||
'context': {
|
||||
'create': False,
|
||||
},
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_contract_overhead(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Overhead Contract'),
|
||||
'res_model': 'overhead.subcontract',
|
||||
'domain': [('job_order_id', '=', self.id)],
|
||||
'context': {
|
||||
'create': False,
|
||||
},
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_material_consume_order(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Consume Orders'),
|
||||
'res_model': 'material.consume',
|
||||
'domain': [('job_order_id', '=', self.id)],
|
||||
'context': {
|
||||
'create': False,
|
||||
},
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
# Button
|
||||
def action_request_material(self):
|
||||
if not self.material_order_ids:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'title': _('Add Material'),
|
||||
'message': "Add Material to Create Material Request",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
material_request_id = self.env['material.requisition'].create({
|
||||
'title': self.title,
|
||||
'company_id': self.company_id.id,
|
||||
'desc': self.task_desc,
|
||||
'project_id': self.project_id.id,
|
||||
'department_id': self.department_id.id,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'user_id': self.user_id.id,
|
||||
'work_order_id': self.id,
|
||||
'work_type_id': self.work_type_id.id,
|
||||
'site_id': self.site_id.id
|
||||
})
|
||||
for data in self.material_order_ids:
|
||||
if data.qty > 0:
|
||||
record = {
|
||||
'material_id': data.material_id.id,
|
||||
'name': data.name,
|
||||
'qty': data.qty,
|
||||
'warehouse_id': self.warehouse_id.id,
|
||||
'material_req_id': material_request_id.id,
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id,
|
||||
}
|
||||
req_line_id = self.env['material.requisition.line'].create(
|
||||
record)
|
||||
data.material_req_line_id = req_line_id.id
|
||||
self.material_req_id = material_request_id.id
|
||||
self.state = 'material_request'
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Material Request'),
|
||||
'res_model': 'material.requisition',
|
||||
'res_id': material_request_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_create_equipment_subcontract(self):
|
||||
for rec in self.equipment_order_ids:
|
||||
if not rec.equip_sub_contract_id:
|
||||
data = {
|
||||
'name': rec.desc,
|
||||
'equipment_id': rec.equipment_id.id,
|
||||
'cost_type': rec.cost_type,
|
||||
'qty': rec.qty,
|
||||
'remain_qty': rec.qty,
|
||||
'cost': rec.cost,
|
||||
'total_cost': rec.total_cost,
|
||||
'vendor_id': rec.vendor_id.id,
|
||||
'job_type_id': self.work_type_id.id,
|
||||
'sub_category_id': rec.sub_category_id.id,
|
||||
'job_order_id': self.id,
|
||||
'remaining_amount': rec.total_cost,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'tax_id': rec.tax_id.id
|
||||
}
|
||||
equip_line_id = self.env['equipment.subcontract'].create(data)
|
||||
rec.equip_sub_contract_id = equip_line_id.id
|
||||
|
||||
def action_create_labour_subcontract(self):
|
||||
for rec in self.labour_order_ids:
|
||||
if not rec.labour_sub_contract_id:
|
||||
data = {
|
||||
'name': rec.name,
|
||||
'product_id': rec.product_id.id,
|
||||
'hours': rec.hours,
|
||||
'remain_hours': rec.hours,
|
||||
'cost': rec.cost,
|
||||
'total_cost': rec.sub_total,
|
||||
'vendor_id': rec.vendor_id.id,
|
||||
'job_type_id': self.work_type_id.id,
|
||||
'sub_category_id': rec.sub_category_id.id,
|
||||
'job_order_id': self.id,
|
||||
'remaining_amount': rec.sub_total,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'tax_id': rec.tax_id.id
|
||||
}
|
||||
labour_line_id = self.env['labour.subcontract'].create(data)
|
||||
rec.labour_sub_contract_id = labour_line_id.id
|
||||
|
||||
def action_create_overhead_subcontract(self):
|
||||
for rec in self.overhead_order_ids:
|
||||
if not rec.overhead_sub_contract_id:
|
||||
data = {
|
||||
'name': rec.name,
|
||||
'product_id': rec.product_id.id,
|
||||
'qty': rec.qty,
|
||||
'remain_qty': rec.qty,
|
||||
'cost': rec.cost,
|
||||
'total_cost': rec.sub_total,
|
||||
'vendor_id': rec.vendor_id.id,
|
||||
'job_type_id': self.work_type_id.id,
|
||||
'sub_category_id': rec.sub_category_id.id,
|
||||
'job_order_id': self.id,
|
||||
'remaining_amount': rec.sub_total,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'tax_id': rec.tax_id.id
|
||||
}
|
||||
overhead_line_id = self.env['overhead.subcontract'].create(
|
||||
data)
|
||||
rec.overhead_sub_contract_id = overhead_line_id.id
|
||||
|
||||
def action_create_material_consume_order(self):
|
||||
remain_qty = False
|
||||
for rec in self.material_order_ids:
|
||||
if not rec.remain_qty == 0:
|
||||
remain_qty = True
|
||||
break
|
||||
if remain_qty:
|
||||
consume_order_id = self.env['material.consume'].create({
|
||||
'job_order_id': self.id,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'warehouse_id': self.warehouse_id.id
|
||||
})
|
||||
for rec in self.material_order_ids:
|
||||
if rec.remain_qty > 0:
|
||||
self.env['material.consume.line'].create({
|
||||
'material_id': rec.material_id.id,
|
||||
'name': rec.name,
|
||||
'qty': rec.remain_qty,
|
||||
'material_consume_id': consume_order_id.id,
|
||||
'material_line_id': rec.id
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Material Consume Order'),
|
||||
'res_model': 'material.consume',
|
||||
'res_id': consume_order_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new'
|
||||
}
|
||||
if not remain_qty:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "No Material consumption orders found !",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
|
||||
def action_department_approval(self):
|
||||
self.stage = 'waiting_approval'
|
||||
|
||||
def action_department_approval_approved(self):
|
||||
self.stage = 'approved'
|
||||
|
||||
def action_department_approval_reject(self):
|
||||
self.stage = 'cancel'
|
||||
|
||||
def action_draft_order(self):
|
||||
self.stage = 'draft'
|
||||
|
||||
def action_cancel_order(self):
|
||||
self.stage = 'cancel'
|
||||
|
||||
def action_in_progress(self):
|
||||
self.state = 'in_progress'
|
||||
|
||||
def action_reset_draft(self):
|
||||
self.state = 'draft'
|
||||
|
||||
def action_complete_work_order(self):
|
||||
material = True
|
||||
equipment = True
|
||||
labour = True
|
||||
overhead = True
|
||||
for data in self.equipment_contract_ids:
|
||||
if data.stage != 'done':
|
||||
equipment = False
|
||||
break
|
||||
for data in self.labour_contract_ids:
|
||||
if data.stage != 'done':
|
||||
labour = False
|
||||
break
|
||||
for data in self.overhead_contract_ids:
|
||||
if data.stage != 'done':
|
||||
overhead = False
|
||||
break
|
||||
for data in self.consume_order_ids:
|
||||
if data.qc_status != 'approve':
|
||||
material = False
|
||||
break
|
||||
if not material or not equipment or not labour or not overhead:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': _(
|
||||
"Please Complete Consume Order / Equipment Subcontract / Labour Subcontract / Overhead Subcontract to complete work order"),
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
self.state = 'complete'
|
||||
|
||||
|
||||
class OrderMaterialLine(models.Model):
|
||||
_name = 'order.material.line'
|
||||
_description = "Job order material line"
|
||||
|
||||
material_id = fields.Many2one(
|
||||
'product.product', string="Material", domain="[('is_material','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
remain_qty = fields.Integer(string="Remain Qty.", default=1)
|
||||
usage_qty = fields.Integer(string="Used Qty.")
|
||||
uom_id = fields.Many2one(related="material_id.uom_po_id", string="UOM")
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
sub_category_id = fields.Many2one(
|
||||
'job.sub.category', string="Work Sub Type")
|
||||
price = fields.Monetary(string="Price")
|
||||
total_price = fields.Monetary(
|
||||
string="Total Price", compute="_compute_total_price")
|
||||
material_req_line_id = fields.Many2one('material.requisition.line')
|
||||
phase_forcast_qty = fields.Float()
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
|
||||
# Budget Field
|
||||
job_sheet_id = fields.Many2one(
|
||||
related="job_order_id.job_sheet_id", store=True)
|
||||
project_id = fields.Many2one(related="job_order_id.project_id", store=True)
|
||||
work_type_id = fields.Many2one(
|
||||
related="job_order_id.work_type_id", store=True)
|
||||
state = fields.Selection(related="job_order_id.state", store=True)
|
||||
|
||||
@api.onchange('material_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.material_id.name
|
||||
rec.price = rec.material_id.standard_price
|
||||
|
||||
@api.depends('qty', 'price', 'tax_id')
|
||||
def _compute_total_price(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.qty and rec.price:
|
||||
total = rec.qty * rec.price
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * total / 100
|
||||
total = tax_amount + total
|
||||
rec.total_price = total
|
||||
|
||||
@api.onchange('qty')
|
||||
def _onchange_remain_qty(self):
|
||||
for rec in self:
|
||||
rec.remain_qty = rec.qty
|
||||
|
||||
@api.depends('warehouse_id', 'material_id')
|
||||
def _compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
qty = 0.0
|
||||
if rec.material_id:
|
||||
qty = rec.material_id.with_context(
|
||||
warehouse=rec.warehouse_id.id).virtual_available
|
||||
rec.forcast_qty = qty
|
||||
|
||||
|
||||
class OrderEquipmentLine(models.Model):
|
||||
_name = 'order.equipment.line'
|
||||
_description = 'Construction Work Order Equipment Line'
|
||||
|
||||
equipment_id = fields.Many2one(
|
||||
'product.product', string="Equipment", domain="[('is_equipment','=',True)]")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
cost_type = fields.Selection(
|
||||
[('depreciation_cost', 'Depreciation Cost'), ('investment_cost', 'Investment/Interest Cost'),
|
||||
('tax', 'Tax'), ('rent', 'Rent'), ('other', 'Other')], string="Type", default='rent')
|
||||
desc = fields.Text(string='Description')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
cost = fields.Monetary(string="Estimation Cost")
|
||||
total_cost = fields.Monetary(
|
||||
string="Total Cost", compute="_compute_total_cost", store=True)
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
purchase_order_id = fields.Many2one(
|
||||
'purchase.order', string="Purchase Order")
|
||||
is_po_create = fields.Boolean()
|
||||
sub_category_id = fields.Many2one(
|
||||
'job.sub.category', string="Work Sub Type")
|
||||
|
||||
equip_sub_contract_id = fields.Many2one(
|
||||
'equipment.subcontract', string="Equip Subcontract")
|
||||
phase_forcast_qty = fields.Float()
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
|
||||
# Budget Field
|
||||
job_sheet_id = fields.Many2one(
|
||||
related="job_order_id.job_sheet_id", store=True)
|
||||
project_id = fields.Many2one(related="job_order_id.project_id", store=True)
|
||||
work_type_id = fields.Many2one(
|
||||
related="job_order_id.work_type_id", store=True)
|
||||
state = fields.Selection(related="job_order_id.state", store=True)
|
||||
|
||||
@api.depends('qty', 'cost', 'tax_id')
|
||||
def _compute_total_cost(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.cost and rec.qty:
|
||||
total = rec.cost * rec.qty
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * total / 100
|
||||
total = tax_amount + total
|
||||
rec.total_cost = total
|
||||
|
||||
@api.onchange('equipment_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.desc = rec.equipment_id.name
|
||||
rec.cost = rec.equipment_id.standard_price
|
||||
|
||||
|
||||
class OrderLabourLine(models.Model):
|
||||
_name = 'order.labour.line'
|
||||
_description = "Work Order Labour Line"
|
||||
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
product_id = fields.Many2one(
|
||||
'product.product', string="Product", domain="[('is_labour','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
hours = fields.Float(string="Hours")
|
||||
remain_hours = fields.Float(
|
||||
related='labour_sub_contract_id.remain_hours', string="Remain Hours")
|
||||
cost = fields.Monetary(string="Cost / Hour")
|
||||
sub_total = fields.Monetary(
|
||||
string="Sub Total", compute="_compute_sub_total", store=True)
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
is_bill_created = fields.Boolean()
|
||||
purchase_order_id = fields.Many2one(
|
||||
'purchase.order', string="Purchase Order")
|
||||
sub_category_id = fields.Many2one(
|
||||
'job.sub.category', string="Work Sub Type")
|
||||
labour_sub_contract_id = fields.Many2one(
|
||||
'labour.subcontract', string="Subcontract")
|
||||
phase_forcast_qty = fields.Float()
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
|
||||
# Budget Field
|
||||
job_sheet_id = fields.Many2one(
|
||||
related="job_order_id.job_sheet_id", store=True)
|
||||
project_id = fields.Many2one(related="job_order_id.project_id", store=True)
|
||||
work_type_id = fields.Many2one(
|
||||
related="job_order_id.work_type_id", store=True)
|
||||
state = fields.Selection(related="job_order_id.state", store=True)
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
rec.cost = rec.product_id.standard_price
|
||||
|
||||
@api.depends('cost', 'hours', 'tax_id')
|
||||
def _compute_sub_total(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.cost and rec.hours:
|
||||
total = rec.cost * rec.hours
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * total / 100
|
||||
total = tax_amount + total
|
||||
rec.sub_total = total
|
||||
|
||||
|
||||
class OrderOverheadLine(models.Model):
|
||||
_name = 'order.overhead.line'
|
||||
_description = "Work Order Overhead Line"
|
||||
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
product_id = fields.Many2one(
|
||||
'product.product', string="Product", domain="[('is_overhead','=',True)]")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
name = fields.Char(string="Description")
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
uom_id = fields.Many2one(
|
||||
related="product_id.uom_po_id", string="Unit of Measure")
|
||||
cost = fields.Monetary(string="Cost / Unit")
|
||||
sub_total = fields.Monetary(
|
||||
string="Sub Total", compute="_compute_sub_total", store=True)
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
is_bill_created = fields.Boolean()
|
||||
purchase_order_id = fields.Many2one(
|
||||
'purchase.order', string="Purchase Order")
|
||||
sub_category_id = fields.Many2one(
|
||||
'job.sub.category', string="Work Sub Type")
|
||||
overhead_sub_contract_id = fields.Many2one(
|
||||
'overhead.subcontract', string="Subcontract")
|
||||
phase_forcast_qty = fields.Float()
|
||||
tax_id = fields.Many2one('account.tax', string="Taxes")
|
||||
|
||||
# Budget Field
|
||||
job_sheet_id = fields.Many2one(
|
||||
related="job_order_id.job_sheet_id", store=True)
|
||||
project_id = fields.Many2one(related="job_order_id.project_id", store=True)
|
||||
work_type_id = fields.Many2one(
|
||||
related="job_order_id.work_type_id", store=True)
|
||||
state = fields.Selection(related="job_order_id.state", store=True)
|
||||
|
||||
@api.depends('cost', 'qty', 'tax_id')
|
||||
def _compute_sub_total(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.cost and rec.qty:
|
||||
total = rec.cost * rec.qty
|
||||
if rec.tax_id:
|
||||
tax_amount = rec.tax_id.amount * total / 100
|
||||
total = tax_amount + total
|
||||
rec.sub_total = total
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
rec.cost = rec.product_id.standard_price
|
||||
735
addons/tk_construction_management/models/material_requisition.py
Normal file
735
addons/tk_construction_management/models/material_requisition.py
Normal file
@@ -0,0 +1,735 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class MaterialRequisition(models.Model):
|
||||
_name = 'material.requisition'
|
||||
_description = "Material Requisition"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string='Sequence', required=True,
|
||||
readonly=True, default=lambda self: _('New'))
|
||||
title = fields.Char(string="Title", tracking=True)
|
||||
|
||||
reject_reason = fields.Text(string="Reject Reason")
|
||||
allow_resubmit = fields.Boolean(string="Allow Resubmit", tracking=True)
|
||||
po_created = fields.Boolean()
|
||||
delivery_ready = fields.Boolean(compute="_compute_delivery_ready")
|
||||
line_added = fields.Boolean()
|
||||
stage = fields.Selection(
|
||||
[('draft', 'Draft'),
|
||||
('department_approval', 'Waiting Department Approval'),
|
||||
('approve', 'In Progress'),
|
||||
('ready_delivery', 'Ready for Delivery'),
|
||||
('reject', 'Reject'),
|
||||
('internal_transfer', 'Internal Transfer'),
|
||||
('material_arrived', 'Material Arrived'),
|
||||
('cancel', 'Cancel')], default='draft', tracking=True)
|
||||
desc = fields.Text(string="Description")
|
||||
|
||||
# Project Details
|
||||
site_id = fields.Many2one('tk.construction.site', string="Project")
|
||||
project_id = fields.Many2one(
|
||||
'tk.construction.project', string="Sub Project", tracking=True)
|
||||
warehouse_id = fields.Many2one(
|
||||
related="project_id.warehouse_id", string="Warehouse")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string="Company", default=lambda self: self.env.company)
|
||||
|
||||
# Other Details
|
||||
date = fields.Datetime(string="Date", default=fields.Datetime.now())
|
||||
responsible_id = fields.Many2one('res.users', default=lambda self: self.env.user and self.env.user.id or False,
|
||||
string="Created By")
|
||||
|
||||
# Department Details
|
||||
department_id = fields.Many2one(
|
||||
'construction.department', string="Department", tracking=True)
|
||||
manager_ids = fields.Many2many('res.users', string="Manager")
|
||||
user_id = fields.Many2one('res.users', string="Responsible User ")
|
||||
|
||||
# Work Type & Job Sheet and Phase
|
||||
work_type_id = fields.Many2one('job.type', string="Work Type")
|
||||
work_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
job_sheet_id = fields.Many2one(
|
||||
related="work_order_id.job_sheet_id", store=True)
|
||||
|
||||
internal_transfer_id = fields.Many2one(
|
||||
'internal.transfer', string="Transfer Ref.")
|
||||
|
||||
# Back Order
|
||||
is_back_order = fields.Boolean(tracking=True)
|
||||
is_any_back_order = fields.Boolean(compute="_compute_any_back_order")
|
||||
back_order_id = fields.Many2one(
|
||||
'material.requisition', string="Back Order", tracking=True)
|
||||
material_req_ref = fields.Char(
|
||||
string="Material Requisition Ref.", tracking=True)
|
||||
|
||||
# One 2 Many
|
||||
material_line_ids = fields.One2many(
|
||||
'material.requisition.line', 'material_req_id')
|
||||
material_purchase_ids = fields.One2many(
|
||||
'material.purchase.line', 'material_req_id')
|
||||
material_transfer_ids = fields.One2many(
|
||||
'material.transfer.line', 'material_req_id')
|
||||
|
||||
# compute
|
||||
po_count = fields.Integer(
|
||||
string="Purchase Order Count", compute="_compute_count")
|
||||
bill_count = fields.Integer(string="Bill Count", compute="_compute_count")
|
||||
delivery_count = fields.Integer(
|
||||
string="Delivery Count", compute="_compute_count")
|
||||
|
||||
# Create, Write, Unlink, Constrain
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('name', _('New')) == _('New'):
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code(
|
||||
'material.req') or _('New')
|
||||
res = super(MaterialRequisition, self).create(vals_list)
|
||||
return res
|
||||
|
||||
def name_get(self):
|
||||
data = []
|
||||
for rec in self:
|
||||
data.append((rec.id, '%s - %s' % (rec.name, rec.title)))
|
||||
return data
|
||||
|
||||
# Compute
|
||||
def _compute_count(self):
|
||||
for rec in self:
|
||||
rec.po_count = self.env['purchase.order'].search_count(
|
||||
[('material_req_id', '=', rec.id)])
|
||||
rec.bill_count = self.env['account.move'].search_count(
|
||||
[('material_req_id', '=', rec.id)])
|
||||
po = self.material_purchase_ids.mapped(
|
||||
'purchase_order_id').mapped('name')
|
||||
ids = self.env['stock.picking'].search(
|
||||
[('origin', 'in', po), ('code', '=', 'incoming')]).mapped('id')
|
||||
rec.delivery_count = self.env['stock.picking'].search_count(
|
||||
[('id', 'in', ids)])
|
||||
|
||||
def _compute_delivery_ready(self):
|
||||
if self.material_purchase_ids:
|
||||
incomplete = False
|
||||
for rec in self.material_purchase_ids:
|
||||
if not rec.status == 'complete':
|
||||
incomplete = True
|
||||
break
|
||||
if not incomplete:
|
||||
self.delivery_ready = True
|
||||
else:
|
||||
self.delivery_ready = False
|
||||
else:
|
||||
self.delivery_ready = True
|
||||
|
||||
@api.depends('material_line_ids')
|
||||
def _compute_any_back_order(self):
|
||||
back_order = False
|
||||
for rec in self.material_line_ids:
|
||||
if rec.operation_type == 'back_order':
|
||||
back_order = True
|
||||
break
|
||||
if back_order:
|
||||
self.is_any_back_order = True
|
||||
else:
|
||||
self.is_any_back_order = False
|
||||
|
||||
# Onchange
|
||||
@api.onchange('department_id', 'manager_ids')
|
||||
def _onchange_department_manager(self):
|
||||
ids = []
|
||||
for rec in self:
|
||||
if rec.department_id:
|
||||
ids = rec.department_id.manager_ids.ids
|
||||
return {'domain': {'manager_ids': [('id', 'in', ids)]}}
|
||||
|
||||
@api.onchange('department_id', 'manager_ids', 'user_id')
|
||||
def _onchange_department_responsible(self):
|
||||
ids = []
|
||||
for rec in self:
|
||||
if rec.department_id:
|
||||
ids = rec.department_id.user_ids.ids
|
||||
return {'domain': {'user_id': [('id', 'in', ids)]}}
|
||||
|
||||
# Smart Button
|
||||
def action_view_purchase_order(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Purchase Order'),
|
||||
'res_model': 'purchase.order',
|
||||
'domain': [('material_req_id', '=', self.id)],
|
||||
'context': {
|
||||
'default_material_req_id': self.id
|
||||
},
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_bills(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Bills'),
|
||||
'res_model': 'account.move',
|
||||
'domain': [('material_req_id', '=', self.id)],
|
||||
'context': {
|
||||
'default_material_req_id': self.id
|
||||
},
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def action_view_delivery_order(self):
|
||||
po = self.material_purchase_ids.mapped(
|
||||
'purchase_order_id').mapped('name')
|
||||
ids = self.env['stock.picking'].search(
|
||||
[('origin', 'in', po)]).mapped('id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Delivery Orders'),
|
||||
'res_model': 'stock.picking',
|
||||
'domain': [('id', 'in', ids), ('code', '=', 'incoming')],
|
||||
'view_mode': 'list,form,kanban',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
# Button
|
||||
def action_department_approval(self):
|
||||
user_id = self.env.user.id
|
||||
msg = "<ul><li>Request for Department Approval of <strong>" + str(
|
||||
self.name) + "</strong>" + " On <strong>" + str(
|
||||
fields.Datetime.now()) + "</strong></li></ul>"
|
||||
self.message_post(body=msg, partner_ids=[user_id])
|
||||
self.stage = 'department_approval'
|
||||
|
||||
def action_approve_requisition(self):
|
||||
material_arrives = True
|
||||
for rec in self.material_line_ids:
|
||||
if not (rec.forcast_qty >= rec.qty):
|
||||
material_arrives = False
|
||||
if material_arrives:
|
||||
self.stage = 'material_arrived'
|
||||
self.work_order_id.state = 'material_arrive'
|
||||
else:
|
||||
for rec in self.material_line_ids:
|
||||
if rec.forcast_qty >= rec.qty:
|
||||
rec.is_created = True
|
||||
ready = True
|
||||
for rec in self.material_line_ids:
|
||||
if not rec.is_created:
|
||||
ready = False
|
||||
if not ready:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "Please Validate Lines to Approve Order",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
self.stage = 'approve'
|
||||
user_id = self.env.user.id
|
||||
msg = "<ul><li>Approval request of <strong>" + str(self.name) + "</strong> has been approved On <strong>" + str(
|
||||
fields.Datetime.now()) + "</strong></li></ul>"
|
||||
self.message_post(body=msg, partner_ids=[user_id])
|
||||
|
||||
def action_draft_requisition(self):
|
||||
self.stage = 'draft'
|
||||
self.allow_resubmit = False
|
||||
|
||||
def action_ready_delivery(self):
|
||||
delivery_warehouse = False
|
||||
for rec in self.material_transfer_ids:
|
||||
if not rec.delivery_warehouse_id:
|
||||
delivery_warehouse = True
|
||||
break
|
||||
if delivery_warehouse:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'title': _('Warehouse Missing'),
|
||||
'message': "Please Select Warehouse in Internal Transfer",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
if not self.material_purchase_ids:
|
||||
self.line_added = True
|
||||
if not self.line_added:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'title': _('Add lines'),
|
||||
'message': "Please add purchase order line to internal transfer",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
if not self.material_transfer_ids:
|
||||
self.stage = 'material_arrived'
|
||||
self.work_order_id.state = 'material_arrive'
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Work Order'),
|
||||
'res_model': 'job.order',
|
||||
'res_id': self.work_order_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
else:
|
||||
self.stage = 'ready_delivery'
|
||||
|
||||
def action_create_purchase_order(self):
|
||||
ready = True
|
||||
for rec in self.material_purchase_ids:
|
||||
if not rec.vendor_id:
|
||||
ready = False
|
||||
if not ready:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'title': ('Vendor or Warehouse Missing !'),
|
||||
'message': "Please Select Warehouse or Vendor in Material Purchase",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
price_check = True
|
||||
for rec in self.material_purchase_ids:
|
||||
if not rec.price > 0:
|
||||
price_check = False
|
||||
if not price_check:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'title': ('Invalid Product Price !'),
|
||||
'message': "Price must greater than zero to create Purchase Order.",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
vendor_count = self.material_purchase_ids.mapped(
|
||||
'vendor_id').mapped('id')
|
||||
warehouse_count = self.material_purchase_ids.mapped(
|
||||
'purchase_warehouse_id').mapped('id')
|
||||
for warehouse in warehouse_count:
|
||||
for data in vendor_count:
|
||||
lines = []
|
||||
create_po_ids = []
|
||||
for rec in self.material_purchase_ids:
|
||||
if rec.purchase_warehouse_id.id == warehouse and not rec.purchase_order_id and rec.vendor_id.id == data:
|
||||
lines.append((0, 0, {
|
||||
'product_id': rec.product_id.id,
|
||||
'name': rec.name,
|
||||
'product_qty': rec.qty,
|
||||
'product_uom': rec.uom_id.id,
|
||||
'price_unit': rec.price,
|
||||
}))
|
||||
create_po_ids.append(rec.id)
|
||||
rec.product_id.last_po_price = rec.price
|
||||
stock_picking_type_id = self.env['stock.picking.type'].search(
|
||||
[('code', '=', 'incoming'), ('warehouse_id', '=', warehouse)], limit=1)
|
||||
if lines and stock_picking_type_id:
|
||||
record = {
|
||||
'partner_id': data,
|
||||
'order_line': lines,
|
||||
'material_req_id': self.id,
|
||||
'picking_type_id': stock_picking_type_id.id
|
||||
}
|
||||
purchase_order_id = self.env['purchase.order'].create(
|
||||
record)
|
||||
for rec in create_po_ids:
|
||||
materia_po_line = self.env['material.purchase.line'].browse(
|
||||
rec)
|
||||
materia_po_line.purchase_order_id = purchase_order_id.id
|
||||
self.po_created = True
|
||||
|
||||
def action_create_back_order(self):
|
||||
ready = True
|
||||
for rec in self.material_line_ids:
|
||||
if not rec.is_created:
|
||||
ready = False
|
||||
if not ready:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': "Please Validate Lines to Create Back Order",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
mrq = {
|
||||
'title': self.title,
|
||||
'project_id': self.project_id.id,
|
||||
'warehouse_id': self.warehouse_id.id,
|
||||
'department_id': self.department_id.id,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'user_id': self.user_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id,
|
||||
'stage': 'approve',
|
||||
'is_back_order': True,
|
||||
'work_type_id': self.work_type_id.id,
|
||||
'work_order_id': self.work_order_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id,
|
||||
'site_id': self.site_id.id,
|
||||
'material_req_ref': self.name
|
||||
}
|
||||
mrq_id = self.env['material.requisition'].create(mrq)
|
||||
self.back_order_id = mrq_id.id
|
||||
for data in self.material_line_ids:
|
||||
if data.operation_type == 'back_order':
|
||||
record = {
|
||||
'product_id': data.material_id.id,
|
||||
'name': data.name,
|
||||
'qty': data.back_order_qty,
|
||||
'price': data.material_id.last_po_price if data.material_id.last_po_price != 0 else data.material_id.standard_price,
|
||||
'purchase_warehouse_id': data.warehouse_id.id,
|
||||
'material_req_id': mrq_id.id,
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
'job_sheet_id': data.job_sheet_id.id
|
||||
}
|
||||
self.env['material.purchase.line'].create(record)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Back Order'),
|
||||
'res_model': 'material.requisition',
|
||||
'res_id': mrq_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
def validate_material_line_all(self):
|
||||
validate = True
|
||||
for rec in self.material_line_ids:
|
||||
if not rec.is_created and (not rec.warehouse_id or not rec.operation_type):
|
||||
validate = False
|
||||
if not validate:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'title': ('Warehouse or operation type Missing !'),
|
||||
'message': "Please Select warehouse or operation Type to validate.",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
for rec in self.material_line_ids:
|
||||
if not rec.is_created:
|
||||
rec.validate_material_line()
|
||||
|
||||
def action_insert_internal_transfer(self):
|
||||
for rec in self.material_purchase_ids:
|
||||
if not rec.purchase_warehouse_id.id == self.project_id.warehouse_id.id:
|
||||
internal_transfer_id = self.env['material.transfer.line'].create({
|
||||
'product_id': rec.product_id.id,
|
||||
'name': rec.name,
|
||||
'pickup_warehouse_id': rec.purchase_warehouse_id.id,
|
||||
'delivery_warehouse_id': self.project_id.warehouse_id.id,
|
||||
'qty': rec.qty,
|
||||
'material_req_id': rec.material_req_id.id,
|
||||
'sub_category_id': rec.sub_category_id.id,
|
||||
'job_sheet_id': rec.job_sheet_id.id,
|
||||
'from_purchase': True
|
||||
})
|
||||
internal_transfer_id._compute_forcast_qty()
|
||||
self.line_added = True
|
||||
|
||||
def action_create_internal_transfer(self):
|
||||
internal_transfer_id = self.env['internal.transfer'].create({
|
||||
'title': 'Internal Transfer of ' + str(self.name),
|
||||
'site_id': self.site_id.id,
|
||||
'project_id': self.project_id.id,
|
||||
'work_type_id': self.work_type_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id,
|
||||
'work_order_id': self.work_order_id.id,
|
||||
'material_req_id': self.id,
|
||||
'department_id': self.department_id.id,
|
||||
'manager_ids': self.manager_ids.ids,
|
||||
'user_id': self.user_id.id,
|
||||
})
|
||||
for data in self.material_transfer_ids:
|
||||
self.env['internal.transfer.line'].create({
|
||||
'product_id': data.product_id.id,
|
||||
'name': data.name,
|
||||
'qty': data.qty,
|
||||
'pickup_warehouse_id': data.pickup_warehouse_id.id,
|
||||
'delivery_warehouse_id': data.delivery_warehouse_id.id,
|
||||
'internal_transfer_id': internal_transfer_id.id,
|
||||
'sub_category_id': data.sub_category_id.id,
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Internal Transfer'),
|
||||
'res_model': 'internal.transfer',
|
||||
'res_id': internal_transfer_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
|
||||
class MaterialRequisitionLine(models.Model):
|
||||
_name = 'material.requisition.line'
|
||||
_description = "Material Requisition Line"
|
||||
|
||||
material_id = fields.Many2one(
|
||||
'product.product', string="Material", domain="[('is_material','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
uom_id = fields.Many2one(
|
||||
related="material_id.uom_po_id", string="Unit of Measure")
|
||||
operation_type = fields.Selection([('purchase_order', 'Purchase Order'),
|
||||
('internal_transfer', 'Internal Transfer'), ('back_order', 'Back Order')],
|
||||
string="Operation Type")
|
||||
forcast_qty = fields.Float(
|
||||
string="Forcast Qty.", compute="_compute_forcast_qty")
|
||||
warehouse_id = fields.Many2one(
|
||||
'stock.warehouse', string="Pickup / Delivery Warehouse")
|
||||
material_req_id = fields.Many2one('material.requisition')
|
||||
is_created = fields.Boolean()
|
||||
forcast_check = fields.Boolean(compute="_compute_forcast_check")
|
||||
sub_category_id = fields.Many2one(
|
||||
'job.sub.category', string="Work Sub Type")
|
||||
stage = fields.Selection(related="material_req_id.stage")
|
||||
job_sheet_id = fields.Many2one('job.costing', string="Job Cost Sheet")
|
||||
remain_qty = fields.Integer(string="Remaining Qty.")
|
||||
back_order_qty = fields.Integer(string="Back Order Qty.")
|
||||
|
||||
@api.depends('receive_qty', 'qty')
|
||||
def _compute_receive_qty(self):
|
||||
for rec in self:
|
||||
if rec.receive_qty and rec.qty:
|
||||
rec.remain_qty = rec.qty - rec.receive_qty
|
||||
else:
|
||||
rec.remain_qty = 0
|
||||
|
||||
@api.onchange('material_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.material_id.name
|
||||
|
||||
@api.depends('forcast_qty', 'qty')
|
||||
def _compute_forcast_check(self):
|
||||
for rec in self:
|
||||
if rec.forcast_qty >= rec.qty:
|
||||
rec.forcast_check = True
|
||||
else:
|
||||
rec.forcast_check = False
|
||||
|
||||
@api.depends('warehouse_id', 'material_id')
|
||||
def _compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
qty = 0.0
|
||||
if rec.material_id:
|
||||
qty = rec.material_id.with_context(
|
||||
warehouse=rec.warehouse_id.id).virtual_available
|
||||
rec.forcast_qty = qty
|
||||
|
||||
def validate_material_line(self):
|
||||
if not self.is_created:
|
||||
if not self.warehouse_id or not self.operation_type:
|
||||
message = {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'title': ('Warehouse or operation type Missing !'),
|
||||
'message': "Please Select warehouse or operation Type to validate.",
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return message
|
||||
if self.operation_type == 'purchase_order':
|
||||
purchase_data = {
|
||||
'product_id': self.material_id.id,
|
||||
'name': self.name,
|
||||
'qty': self.qty,
|
||||
'purchase_warehouse_id': self.warehouse_id.id,
|
||||
'material_req_id': self.material_req_id.id,
|
||||
'sub_category_id': self.sub_category_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id,
|
||||
'price': self.material_id.last_po_price if self.material_id.last_po_price != 0 else self.material_id.standard_price
|
||||
}
|
||||
self.env['material.purchase.line'].create(purchase_data)
|
||||
if self.operation_type == 'internal_transfer':
|
||||
internal_data = {
|
||||
'product_id': self.material_id.id,
|
||||
'name': self.name,
|
||||
'qty': self.qty,
|
||||
'pickup_warehouse_id': self.warehouse_id.id,
|
||||
'delivery_warehouse_id': self.material_req_id.project_id.warehouse_id.id,
|
||||
'material_req_id': self.material_req_id.id,
|
||||
'sub_category_id': self.sub_category_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id
|
||||
}
|
||||
if internal_data['pickup_warehouse_id'] == internal_data['delivery_warehouse_id']:
|
||||
pass
|
||||
else:
|
||||
self.env['material.transfer.line'].create(internal_data)
|
||||
if self.operation_type == 'back_order':
|
||||
if self.qty > self.forcast_qty:
|
||||
self.back_order_qty = self.qty - self.forcast_qty
|
||||
internal_data = {
|
||||
'product_id': self.material_id.id,
|
||||
'name': self.name,
|
||||
'qty': self.forcast_qty,
|
||||
'pickup_warehouse_id': self.warehouse_id.id,
|
||||
'delivery_warehouse_id': self.material_req_id.project_id.warehouse_id.id,
|
||||
'material_req_id': self.material_req_id.id,
|
||||
'sub_category_id': self.sub_category_id.id,
|
||||
'job_sheet_id': self.job_sheet_id.id
|
||||
}
|
||||
if internal_data['pickup_warehouse_id'] == internal_data['delivery_warehouse_id']:
|
||||
pass
|
||||
else:
|
||||
self.env['material.transfer.line'].create(
|
||||
internal_data)
|
||||
self.is_created = True
|
||||
|
||||
|
||||
class MaterialPurchaseLine(models.Model):
|
||||
_name = 'material.purchase.line'
|
||||
_description = "Material Purchase Line"
|
||||
|
||||
product_id = fields.Many2one('product.product', string="Product")
|
||||
name = fields.Char(string="Description")
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id', string='Currency')
|
||||
forcast_qty = fields.Float(
|
||||
string="Forcast Qty.", compute="_compute_forcast_qty")
|
||||
price = fields.Monetary(string="Price")
|
||||
total_price = fields.Monetary(
|
||||
string="Total Price", compute="_compute_total_price")
|
||||
uom_id = fields.Many2one(
|
||||
related="product_id.uom_po_id", string="Unit of Measure")
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
purchase_warehouse_id = fields.Many2one(
|
||||
'stock.warehouse', string="Delivery Warehouse")
|
||||
material_req_id = fields.Many2one('material.requisition')
|
||||
purchase_order_id = fields.Many2one(
|
||||
'purchase.order', string="Purchase Order")
|
||||
status = fields.Selection([('incomplete', 'Incomplete'), ('partial_complete', 'Partial Complete'),
|
||||
('complete', 'Complete')], compute="_compute_po_delivery_status")
|
||||
sub_category_id = fields.Many2one(
|
||||
'job.sub.category', string="Work Sub Type")
|
||||
job_sheet_id = fields.Many2one('job.costing', string="Job Cost Sheet")
|
||||
|
||||
def unlink(self):
|
||||
if not self.purchase_order_id:
|
||||
return super(MaterialPurchaseLine, self).unlink()
|
||||
else:
|
||||
raise ValidationError(
|
||||
_('You can not delete material purchase line after creating purchase order'))
|
||||
|
||||
@api.depends('purchase_warehouse_id', 'product_id')
|
||||
def _compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
qty = 0.0
|
||||
if rec.product_id:
|
||||
qty = rec.product_id.with_context(
|
||||
warehouse=rec.purchase_warehouse_id.id).virtual_available
|
||||
rec.forcast_qty = qty
|
||||
|
||||
@api.depends('price', 'qty')
|
||||
def _compute_total_price(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.qty and rec.price:
|
||||
total = rec.qty * rec.price
|
||||
rec.total_price = total
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
|
||||
@api.depends('purchase_order_id')
|
||||
def _compute_po_delivery_status(self):
|
||||
for rec in self:
|
||||
delivery_orders = self.env['stock.picking'].search(
|
||||
[('code', '=', 'incoming'), ('origin', '=', rec.purchase_order_id.name)])
|
||||
if rec.purchase_order_id and delivery_orders:
|
||||
incomplete, complete = False, False
|
||||
for data in delivery_orders:
|
||||
if data.state == 'done':
|
||||
complete = True
|
||||
else:
|
||||
incomplete = True
|
||||
if complete and not incomplete:
|
||||
rec.status = 'complete'
|
||||
elif incomplete and not complete:
|
||||
rec.status = 'incomplete'
|
||||
else:
|
||||
rec.status = "partial_complete"
|
||||
else:
|
||||
rec.status = ""
|
||||
|
||||
@api.onchange('product_id', 'vendor_id')
|
||||
def onchange_product_vendor_price_list(self):
|
||||
for rec in self:
|
||||
if rec.vendor_id and rec.product_id:
|
||||
domain = [('partner_id', '=', rec.vendor_id.id), '|',
|
||||
('product_tmpl_id', '=', rec.product_id.product_tmpl_id.id),
|
||||
('product_id', '=', rec.product_id.id)]
|
||||
vendor_price = self.env['product.supplierinfo'].search(
|
||||
domain, limit=1, order='create_date desc')
|
||||
if vendor_price.price > 0:
|
||||
rec.price = vendor_price.price
|
||||
elif rec.product_id.last_po_price > 0:
|
||||
rec.price = rec.product_id.last_po_price
|
||||
else:
|
||||
rec.price = rec.product_id.standard_price
|
||||
|
||||
|
||||
class MaterialTransferLine(models.Model):
|
||||
_name = 'material.transfer.line'
|
||||
_description = "Material Transfer Line"
|
||||
|
||||
product_id = fields.Many2one('product.product', string="Product")
|
||||
name = fields.Char(string="Description")
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
forcast_qty = fields.Float(
|
||||
string="Forcast Qty.", compute="_compute_forcast_qty")
|
||||
pickup_warehouse_id = fields.Many2one(
|
||||
'stock.warehouse', string="Picking Warehouse")
|
||||
delivery_warehouse_id = fields.Many2one(
|
||||
'stock.warehouse', string="Delivery Warehouse")
|
||||
material_req_id = fields.Many2one('material.requisition')
|
||||
sub_category_id = fields.Many2one(
|
||||
'job.sub.category', string="Work Sub Type")
|
||||
job_sheet_id = fields.Many2one('job.costing', string="Job Cost Sheet")
|
||||
from_purchase = fields.Boolean(string="From Purchase")
|
||||
|
||||
@api.depends('pickup_warehouse_id', 'product_id')
|
||||
def _compute_forcast_qty(self):
|
||||
for rec in self:
|
||||
qty = 0.0
|
||||
if rec.product_id:
|
||||
qty = rec.product_id.with_context(
|
||||
warehouse=rec.pickup_warehouse_id.id).virtual_available
|
||||
rec.forcast_qty = qty
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_desc(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name
|
||||
18
addons/tk_construction_management/models/project_task.py
Normal file
18
addons/tk_construction_management/models/project_task.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models
|
||||
|
||||
|
||||
class ConProject(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
construction_project_id = fields.Many2one('tk.construction.project', string="Construction Building")
|
||||
|
||||
|
||||
class ConstructionTask(models.Model):
|
||||
_inherit = 'project.task'
|
||||
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
is_inspection_task = fields.Boolean(string="Inspection Task")
|
||||
con_project_id = fields.Many2one('tk.construction.project', string="Construction Project")
|
||||
237
addons/tk_construction_management/models/rate_analysis.py
Normal file
237
addons/tk_construction_management/models/rate_analysis.py
Normal file
@@ -0,0 +1,237 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2022-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class RateAnalysis(models.Model):
|
||||
_name = 'rate.analysis'
|
||||
_description = "Rate Analysis"
|
||||
_rec_name = 'name'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string="Title")
|
||||
site_id = fields.Many2one('tk.construction.site', string="Project")
|
||||
project_id = fields.Many2one('tk.construction.project', string="Sub Project",domain="[('construction_site_id','=',site_id)]")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
activity_id = fields.Many2one('job.type', string="Work Type")
|
||||
sub_activity_ids = fields.Many2many(related="activity_id.sub_category_ids", string="Sub Activities")
|
||||
sub_activity_id = fields.Many2one('job.sub.category', string="Work Sub Type",domain="[('id','in',sub_activity_ids)]")
|
||||
date = fields.Date(string="Date", default=fields.Date.today())
|
||||
unit_id = fields.Many2one('uom.uom', string="Unit")
|
||||
|
||||
# Rate Analysis Type
|
||||
material_analysis_ids = fields.One2many('rate.analysis.material', 'rate_analysis_id', string="Rate Analysis Material")
|
||||
equipment_analysis_ids = fields.One2many('rate.analysis.equipment', 'rate_analysis_id',string="Rate Analysis Equipment")
|
||||
labour_analysis_ids = fields.One2many('rate.analysis.labour', 'rate_analysis_id',string="Rate Analysis Labour")
|
||||
overhead_analysis_ids = fields.One2many('rate.analysis.overhead', 'rate_analysis_id',string="Rate Analysis Overhead")
|
||||
|
||||
# Amount
|
||||
tax_amount = fields.Monetary(string="Tax Amount", compute="compute_total_amount")
|
||||
untaxed_amount = fields.Monetary(string="Untaxed Amount", compute="compute_total_amount")
|
||||
total_amount = fields.Monetary(string="Total Amount", compute="compute_total_amount")
|
||||
|
||||
@api.depends('material_analysis_ids', 'equipment_analysis_ids', 'labour_analysis_ids', 'overhead_analysis_ids')
|
||||
def compute_total_amount(self):
|
||||
for rec in self:
|
||||
tax_amount = 0.0
|
||||
untaxed_amount = 0.0
|
||||
total_amount = 0.0
|
||||
for data in rec.material_analysis_ids:
|
||||
tax_amount = tax_amount + data.tax_amount
|
||||
untaxed_amount = untaxed_amount + data.untaxed_amount
|
||||
total_amount = total_amount + data.total_amount
|
||||
for data in rec.equipment_analysis_ids:
|
||||
tax_amount = tax_amount + data.tax_amount
|
||||
untaxed_amount = untaxed_amount + data.untaxed_amount
|
||||
total_amount = total_amount + data.total_amount
|
||||
for data in rec.labour_analysis_ids:
|
||||
tax_amount = tax_amount + data.tax_amount
|
||||
untaxed_amount = untaxed_amount + data.untaxed_amount
|
||||
total_amount = total_amount + data.total_amount
|
||||
for data in rec.overhead_analysis_ids:
|
||||
tax_amount = tax_amount + data.tax_amount
|
||||
untaxed_amount = untaxed_amount + data.untaxed_amount
|
||||
total_amount = total_amount + data.total_amount
|
||||
rec.tax_amount = tax_amount
|
||||
rec.untaxed_amount = untaxed_amount
|
||||
rec.total_amount = total_amount
|
||||
|
||||
@api.constrains('material_analysis_ids')
|
||||
def _check_cost_material_uniq_product(self):
|
||||
for record in self.material_analysis_ids:
|
||||
duplicates = self.material_analysis_ids.filtered(
|
||||
lambda r: r.id != record.id and r.product_id.id == record.product_id.id)
|
||||
if duplicates:
|
||||
raise ValidationError(_("Material already added !"))
|
||||
|
||||
@api.constrains('equipment_analysis_ids')
|
||||
def _check_cost_equipment_uniq_product(self):
|
||||
for record in self.equipment_analysis_ids:
|
||||
duplicates = self.equipment_analysis_ids.filtered(
|
||||
lambda r: r.id != record.id and r.product_id.id == record.product_id.id)
|
||||
if duplicates:
|
||||
raise ValidationError(_("Equipment already added !"))
|
||||
|
||||
@api.constrains('labour_analysis_ids')
|
||||
def _check_cost_labour_uniq_product(self):
|
||||
for record in self.labour_analysis_ids:
|
||||
duplicates = self.labour_analysis_ids.filtered(
|
||||
lambda r: r.id != record.id and r.product_id.id == record.product_id.id)
|
||||
if duplicates:
|
||||
raise ValidationError(_("Labour already added !"))
|
||||
|
||||
@api.constrains('overhead_analysis_ids')
|
||||
def _check_cost_overhead_uniq_product(self):
|
||||
for record in self.overhead_analysis_ids:
|
||||
duplicates = self.overhead_analysis_ids.filtered(
|
||||
lambda r: r.id != record.id and r.product_id.id == record.product_id.id)
|
||||
if duplicates:
|
||||
raise ValidationError(_("Overhead already added !"))
|
||||
|
||||
|
||||
|
||||
class RateAnalysisMaterial(models.Model):
|
||||
_name = "rate.analysis.material"
|
||||
_description = "Rate Analysis Material Line"
|
||||
|
||||
rate_analysis_id = fields.Many2one('rate.analysis', string="Rate Analysis")
|
||||
product_id = fields.Many2one('product.product', string="Material", domain="[('is_material','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
code = fields.Char(related="product_id.default_code", string="Code")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
uom_id = fields.Many2one(related="product_id.uom_po_id", string="UOM")
|
||||
price = fields.Monetary(string="Price")
|
||||
tax_id = fields.Many2one('account.tax', string="Tax")
|
||||
untaxed_amount = fields.Monetary(string="Untaxed Amount", compute="compute_total")
|
||||
tax_amount = fields.Monetary(string="Tax Amount", compute="compute_total")
|
||||
total_amount = fields.Monetary(string="Total Amount", compute="compute_total")
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_info(self):
|
||||
for rec in self:
|
||||
if rec.product_id:
|
||||
rec.name = rec.product_id.name
|
||||
rec.price = rec.product_id.standard_price
|
||||
|
||||
@api.depends('price', 'qty', 'tax_id.amount', 'tax_id')
|
||||
def compute_total(self):
|
||||
for rec in self:
|
||||
untaxed_amount = rec.qty * rec.price
|
||||
tax_amount = (rec.tax_id.amount * untaxed_amount / 100) if rec.tax_id else 0.0
|
||||
total_amount = untaxed_amount + tax_amount
|
||||
rec.untaxed_amount = untaxed_amount
|
||||
rec.tax_amount = tax_amount
|
||||
rec.total_amount = total_amount
|
||||
|
||||
|
||||
class RateAnalysisEquipment(models.Model):
|
||||
_name = "rate.analysis.equipment"
|
||||
_description = "Rate Analysis Equipment Line"
|
||||
|
||||
rate_analysis_id = fields.Many2one('rate.analysis', string="Rate Analysis")
|
||||
product_id = fields.Many2one('product.product', string="Equipment", domain="[('is_equipment','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
code = fields.Char(related="product_id.default_code", string="Code")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
uom_id = fields.Many2one(related="product_id.uom_po_id", string="UOM")
|
||||
price = fields.Monetary(string="Price")
|
||||
tax_id = fields.Many2one('account.tax', string="Tax")
|
||||
untaxed_amount = fields.Monetary(string="Untaxed Amount", compute="compute_total")
|
||||
tax_amount = fields.Monetary(string="Tax Amount", compute="compute_total")
|
||||
total_amount = fields.Monetary(string="Total Amount", compute="compute_total")
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_info(self):
|
||||
for rec in self:
|
||||
if rec.product_id:
|
||||
rec.name = rec.product_id.name
|
||||
rec.price = rec.product_id.standard_price
|
||||
|
||||
@api.depends('price', 'qty', 'tax_id.amount', 'tax_id')
|
||||
def compute_total(self):
|
||||
for rec in self:
|
||||
untaxed_amount = rec.qty * rec.price
|
||||
tax_amount = (rec.tax_id.amount * untaxed_amount / 100) if rec.tax_id else 0.0
|
||||
total_amount = untaxed_amount + tax_amount
|
||||
rec.untaxed_amount = untaxed_amount
|
||||
rec.tax_amount = tax_amount
|
||||
rec.total_amount = total_amount
|
||||
|
||||
|
||||
class RateAnalysisLabour(models.Model):
|
||||
_name = "rate.analysis.labour"
|
||||
_description = "Rate Analysis Labour Line"
|
||||
|
||||
rate_analysis_id = fields.Many2one('rate.analysis', string="Rate Analysis")
|
||||
product_id = fields.Many2one('product.product', string="Labour", domain="[('is_labour','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
code = fields.Char(related="product_id.default_code", string="Code")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
uom_id = fields.Many2one(related="product_id.uom_po_id", string="UOM")
|
||||
price = fields.Monetary(string="Price")
|
||||
tax_id = fields.Many2one('account.tax', string="Tax")
|
||||
untaxed_amount = fields.Monetary(string="Untaxed Amount", compute="compute_total")
|
||||
tax_amount = fields.Monetary(string="Tax Amount", compute="compute_total")
|
||||
total_amount = fields.Monetary(string="Total Amount", compute="compute_total")
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_info(self):
|
||||
for rec in self:
|
||||
if rec.product_id:
|
||||
rec.name = rec.product_id.name
|
||||
rec.price = rec.product_id.standard_price
|
||||
|
||||
@api.depends('price', 'qty', 'tax_id.amount', 'tax_id')
|
||||
def compute_total(self):
|
||||
for rec in self:
|
||||
untaxed_amount = rec.qty * rec.price
|
||||
tax_amount = (rec.tax_id.amount * untaxed_amount / 100) if rec.tax_id else 0.0
|
||||
total_amount = untaxed_amount + tax_amount
|
||||
rec.untaxed_amount = untaxed_amount
|
||||
rec.tax_amount = tax_amount
|
||||
rec.total_amount = total_amount
|
||||
|
||||
|
||||
class RateAnalysisOverhead(models.Model):
|
||||
_name = "rate.analysis.overhead"
|
||||
_description = "Rate Analysis Overhead Line"
|
||||
|
||||
rate_analysis_id = fields.Many2one('rate.analysis', string="Rate Analysis")
|
||||
product_id = fields.Many2one('product.product', string="Overhead", domain="[('is_overhead','=',True)]")
|
||||
name = fields.Char(string="Description")
|
||||
code = fields.Char(related="product_id.default_code", string="Code")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
uom_id = fields.Many2one(related="product_id.uom_po_id", string="UOM")
|
||||
price = fields.Monetary(string="Price")
|
||||
tax_id = fields.Many2one('account.tax', string="Tax")
|
||||
untaxed_amount = fields.Monetary(string="Untaxed Amount", compute="compute_total")
|
||||
tax_amount = fields.Monetary(string="Tax Amount", compute="compute_total")
|
||||
total_amount = fields.Monetary(string="Total Amount", compute="compute_total")
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_info(self):
|
||||
for rec in self:
|
||||
if rec.product_id:
|
||||
rec.name = rec.product_id.name
|
||||
rec.price = rec.product_id.standard_price
|
||||
|
||||
@api.depends('price', 'qty', 'tax_id.amount', 'tax_id')
|
||||
def compute_total(self):
|
||||
for rec in self:
|
||||
untaxed_amount = rec.qty * rec.price
|
||||
tax_amount = (rec.tax_id.amount * untaxed_amount / 100) if rec.tax_id else 0.0
|
||||
total_amount = untaxed_amount + tax_amount
|
||||
rec.untaxed_amount = untaxed_amount
|
||||
rec.tax_amount = tax_amount
|
||||
rec.total_amount = total_amount
|
||||
11
addons/tk_construction_management/models/res_config.py
Normal file
11
addons/tk_construction_management/models/res_config.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ConstructionResConfig(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
phase_prefix = fields.Char(string='Phase Prefix', default="PHASE/",
|
||||
config_parameter='tk_construction_management.phase_prefix')
|
||||
10
addons/tk_construction_management/models/res_partner.py
Normal file
10
addons/tk_construction_management/models/res_partner.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, api, models
|
||||
|
||||
|
||||
class ConstructionPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
stack_holder = fields.Boolean(string="Stockholder")
|
||||
813
addons/tk_construction_management/models/sub_contract.py
Normal file
813
addons/tk_construction_management/models/sub_contract.py
Normal file
@@ -0,0 +1,813 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020-Today TechKhedut.
|
||||
# Part of TechKhedut. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class EquipmentSubcontract(models.Model):
|
||||
_name = 'equipment.subcontract'
|
||||
_description = "Equipment Subcontract"
|
||||
_rec_name = 'seq'
|
||||
|
||||
seq = fields.Char(string='Sequence', required=True, readonly=True, default=lambda self: _('New'))
|
||||
name = fields.Char(string="Title")
|
||||
equipment_id = fields.Many2one('product.product', string="Equipment", domain="[('is_equipment','=',True)]")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
cost_type = fields.Selection([('depreciation_cost', 'Depreciation Cost'),
|
||||
('investment_cost', 'Investment/Interest Cost'),
|
||||
('tax', 'Tax'),
|
||||
('rent', 'Rent'),
|
||||
('other', 'Other')], string="Type ", default='rent')
|
||||
cost = fields.Monetary(string="Estimation Cost")
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
purchase_order_id = fields.Many2one('purchase.order', string="Purchase Order")
|
||||
job_type_id = fields.Many2one('job.type', string="Work Type")
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
stage = fields.Selection([('draft', 'Draft'), ('in_progress', 'In Progress'), ('done', 'Done')], default='draft')
|
||||
po_bill = fields.Selection([('bill', 'Bill'), ('purchase_order', 'Purchase Order')], string="Type", default='bill')
|
||||
ra_bill_ids = fields.One2many('equip.contract.line', 'contract_id', string="Ra Bills")
|
||||
completion_date = fields.Date(string="Completion Date", compute="compute_completion_date")
|
||||
|
||||
# Calculation
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
remain_qty = fields.Integer(string="Remaining Qty")
|
||||
total_cost = fields.Monetary(string="Total Cost")
|
||||
remaining_amount = fields.Monetary(string="Remaining Amount")
|
||||
progress = fields.Float(string="Complete Billing", compute="_compute_payment_progress")
|
||||
tax_id = fields.Many2one('account.tax', string='Tax')
|
||||
|
||||
# Project Details
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
phase_id = fields.Many2one(related="job_order_id.job_sheet_id", string="Project Phase(WBS)", store=True)
|
||||
project_id = fields.Many2one(related='job_order_id.project_id', string="Sub Project", store=True)
|
||||
task_id = fields.Many2one(related="job_order_id.task_id", string="Task", store=True)
|
||||
|
||||
# Department
|
||||
department_id = fields.Many2one(related="job_order_id.department_id", string="Department", store=True)
|
||||
manager_ids = fields.Many2many('res.users', store=True, string="Manager")
|
||||
user_id = fields.Many2one(related="job_order_id.user_id", string="Responsible")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('seq', _('New')) == _('New'):
|
||||
vals['seq'] = self.env['ir.sequence'].next_by_code('equip.sub') or _('New')
|
||||
res = super(EquipmentSubcontract, self).create(vals_list)
|
||||
return res
|
||||
|
||||
def action_in_progress(self):
|
||||
self.stage = 'in_progress'
|
||||
|
||||
def action_state_done(self):
|
||||
self.stage = 'done'
|
||||
|
||||
@api.constrains('ra_bill_ids', 'qty')
|
||||
def _check_ra_bill_qty(self):
|
||||
qty = 0
|
||||
for record in self.ra_bill_ids:
|
||||
if record.qc_status != 'reject':
|
||||
qty = qty + record.qty
|
||||
if qty > self.qty:
|
||||
raise ValidationError(_("Quantity should be less than total qty."))
|
||||
|
||||
@api.depends('total_cost', 'remaining_amount')
|
||||
def _compute_payment_progress(self):
|
||||
for rec in self:
|
||||
progress = 0.0
|
||||
if rec.total_cost and rec.remaining_amount:
|
||||
progress = (rec.remaining_amount * 100) / rec.total_cost
|
||||
rec.progress = 100 - progress
|
||||
|
||||
@api.depends('stage', 'ra_bill_ids')
|
||||
def compute_completion_date(self):
|
||||
for rec in self:
|
||||
date = None
|
||||
if rec.stage == "done":
|
||||
dates = rec.ra_bill_ids.mapped('date')
|
||||
if dates:
|
||||
date = dates[-1]
|
||||
rec.completion_date = date
|
||||
|
||||
|
||||
class EquipContractLine(models.Model):
|
||||
_name = 'equip.contract.line'
|
||||
_description = "Equip Contract Line"
|
||||
_rec_name = 'remark'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
contract_id = fields.Many2one('equipment.subcontract', string="Subcontract", ondelete='cascade')
|
||||
percentage = fields.Float(string="Percentage", tracking=True, compute="_compute_percentage")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
amount = fields.Monetary(tracking=True, string="Amount", compute="_compute_percentage_amount")
|
||||
date = fields.Date(string="Date", default=fields.Date.today(), tracking=True)
|
||||
remark = fields.Char(string="Remark", tracking=True)
|
||||
purchase_order_id = fields.Many2one('purchase.order', string="Purchase Order")
|
||||
bill_id = fields.Many2one('account.move', string="Bill")
|
||||
payment_state = fields.Selection(related="bill_id.payment_state", string="Payment State", tracking=True)
|
||||
state = fields.Selection(related="purchase_order_id.state", string="State", tracking=True)
|
||||
po_bill = fields.Selection(related="contract_id.po_bill")
|
||||
qty = fields.Integer(string="Qty")
|
||||
retention_percentage = fields.Float(string="Retention(%)")
|
||||
retention_amount = fields.Monetary(string="Retention Amount", compute="compute_retention_amount")
|
||||
final_amount = fields.Monetary(string="Total Amount", compute="_compute_final_amount")
|
||||
|
||||
# QC Check
|
||||
qc_user_id = fields.Many2one('res.users', string="QC Responsible", tracking=True)
|
||||
qc_status = fields.Selection(
|
||||
[('draft', 'Draft'),
|
||||
('request', 'Department Approval'),
|
||||
('approve', 'Approve'),
|
||||
('reject', 'Reject')], default='draft', string="Quality Check Status", tracking=True)
|
||||
reject_reason = fields.Text(string="Reject Reason", tracking=True)
|
||||
|
||||
def unlink(self):
|
||||
for rec in self:
|
||||
if rec.qc_status != 'draft':
|
||||
raise ValidationError(_("You can't delete until Quality Check status is in Draft"))
|
||||
else:
|
||||
return super(EquipContractLine, self).unlink()
|
||||
|
||||
@api.constrains('qty')
|
||||
def _check_ra_bill_qty(self):
|
||||
ra_bill_ids = self.env['equip.contract.line'].search([('contract_id', '=', self.contract_id.id)])
|
||||
qty = 0
|
||||
for record in ra_bill_ids:
|
||||
if record.qc_status != 'reject':
|
||||
qty = qty + record.qty
|
||||
if qty > self.contract_id.qty:
|
||||
raise ValidationError(_("Quantity should be less than total qty."))
|
||||
|
||||
@api.depends('contract_id', 'qty', 'contract_id.tax_id')
|
||||
def _compute_percentage_amount(self):
|
||||
for rec in self:
|
||||
amount = 0.0
|
||||
if rec.qty and rec.contract_id:
|
||||
amount = (rec.contract_id.cost + (rec.contract_id.tax_id.amount * rec.contract_id.cost / 100)) * rec.qty
|
||||
rec.amount = amount
|
||||
|
||||
@api.depends('qty', 'retention_amount')
|
||||
def _compute_final_amount(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.amount:
|
||||
total = rec.amount - rec.retention_amount
|
||||
rec.final_amount = total
|
||||
|
||||
@api.depends('amount', 'retention_percentage')
|
||||
def compute_retention_amount(self):
|
||||
for rec in self:
|
||||
retention_amount = 0.0
|
||||
if rec.retention_percentage:
|
||||
retention_amount = rec.amount * rec.retention_percentage / 100
|
||||
rec.retention_amount = retention_amount
|
||||
|
||||
@api.depends('contract_id', 'amount')
|
||||
def _compute_percentage(self):
|
||||
for rec in self:
|
||||
percentage = 0.0
|
||||
if rec.contract_id and rec.amount > 0:
|
||||
percentage = (100 * rec.amount) / rec.contract_id.total_cost
|
||||
rec.percentage = percentage
|
||||
|
||||
def action_quality_check(self):
|
||||
self.qc_status = 'request'
|
||||
|
||||
def action_quality_check_approve(self):
|
||||
self.qc_status = 'approve'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_quality_check_reject(self):
|
||||
self.qc_status = 'reject'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.qc_status = 'draft'
|
||||
|
||||
def action_create_ra_bill(self):
|
||||
if self.po_bill == 'bill':
|
||||
record = {
|
||||
'product_id': self.contract_id.equipment_id.id,
|
||||
'name': self.contract_id.name,
|
||||
'quantity': 1,
|
||||
'price_unit': self.final_amount,
|
||||
'tax_ids': False
|
||||
}
|
||||
invoice_lines = [(0, 0, record)]
|
||||
data = {
|
||||
'partner_id': self.contract_id.vendor_id.id,
|
||||
'invoice_date': self.date,
|
||||
'invoice_line_ids': invoice_lines,
|
||||
'move_type': 'in_invoice',
|
||||
'equipment_subcontract_id': self.contract_id.id,
|
||||
'job_order_id': self.contract_id.job_order_id.id,
|
||||
'purchase_order': 'equipment'
|
||||
}
|
||||
invoice_id = self.env['account.move'].sudo().create(data)
|
||||
self.bill_id = invoice_id.id
|
||||
remaining_amount = self.contract_id.remaining_amount
|
||||
self.contract_id.remaining_amount = remaining_amount - self.amount
|
||||
qty = self.contract_id.remain_qty
|
||||
self.contract_id.remain_qty = qty - self.qty
|
||||
elif self.po_bill == 'purchase_order':
|
||||
purchase_record = {
|
||||
'product_id': self.contract_id.equipment_id.id,
|
||||
'name': self.contract_id.name,
|
||||
'product_qty': 1,
|
||||
'price_unit': self.final_amount,
|
||||
}
|
||||
purchase_lines = [(0, 0, purchase_record)]
|
||||
purchase_data = {
|
||||
'partner_id': self.contract_id.vendor_id.id,
|
||||
'order_line': purchase_lines,
|
||||
'job_order_id': self.contract_id.job_order_id.id,
|
||||
'equipment_subcontract_id': self.contract_id.id,
|
||||
'purchase_order': 'equipment'
|
||||
}
|
||||
purchase_order_id = self.env['purchase.order'].create(purchase_data)
|
||||
self.purchase_order_id = purchase_order_id.id
|
||||
remaining_amount = self.contract_id.remaining_amount
|
||||
self.contract_id.remaining_amount = remaining_amount - self.amount
|
||||
qty = self.contract_id.remain_qty
|
||||
self.contract_id.remain_qty = qty - self.qty
|
||||
|
||||
|
||||
class LabourSubcontract(models.Model):
|
||||
_name = 'labour.subcontract'
|
||||
_description = "Labour Sub Contract"
|
||||
_rec_name = 'seq'
|
||||
|
||||
seq = fields.Char(string='Sequence', required=True, readonly=True, default=lambda self: _('New'))
|
||||
name = fields.Char(string="Title")
|
||||
product_id = fields.Many2one('product.product', string="Product", domain="[('is_labour','=',True)]")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
cost = fields.Monetary(string="Cost / Hour")
|
||||
po_bill = fields.Selection([('bill', 'Bill'), ('purchase_order', 'Purchase Order')], string="Type",
|
||||
default='bill')
|
||||
stage = fields.Selection([('draft', 'Draft'), ('in_progress', 'In Progress'), ('done', 'Done')],
|
||||
default='draft')
|
||||
vendor_id = fields.Many2one('res.partner', string="Contractor")
|
||||
job_type_id = fields.Many2one('job.type', string="Work Type")
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
ra_bill_ids = fields.One2many('labour.contract.line', 'contract_id', string="Ra Bills")
|
||||
|
||||
# Project Details
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
phase_id = fields.Many2one(related="job_order_id.job_sheet_id", string="Project Phase(WBS)", store=True)
|
||||
project_id = fields.Many2one(related='job_order_id.project_id', string="Sub Project", store=True)
|
||||
task_id = fields.Many2one(related="job_order_id.task_id", string="Task", store=True)
|
||||
|
||||
# Department
|
||||
department_id = fields.Many2one(related="job_order_id.department_id", string="Department", store=True)
|
||||
manager_ids = fields.Many2many('res.users', store=True, string="Manager")
|
||||
user_id = fields.Many2one(related="job_order_id.user_id", string="Responsible")
|
||||
|
||||
# Calculation
|
||||
hours = fields.Float(string="Hours")
|
||||
remain_hours = fields.Float(string="Remaining Hours")
|
||||
total_cost = fields.Monetary(string="Total Cost")
|
||||
remaining_amount = fields.Monetary(string="Remaining Amount")
|
||||
progress = fields.Float(string="Completed Billing", compute="_compute_payment_progress")
|
||||
tax_id = fields.Many2one('account.tax', string='Tax')
|
||||
completion_date = fields.Date(string="Completion Date", compute="compute_completion_date")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('seq', _('New')) == _('New'):
|
||||
vals['seq'] = self.env['ir.sequence'].next_by_code('labour.sub') or _('New')
|
||||
res = super(LabourSubcontract, self).create(vals_list)
|
||||
return res
|
||||
|
||||
def action_in_progress(self):
|
||||
self.stage = 'in_progress'
|
||||
|
||||
def action_state_done(self):
|
||||
self.stage = 'done'
|
||||
|
||||
@api.constrains('ra_bill_ids', 'hours')
|
||||
def _check_ra_bill_hours(self):
|
||||
hours = 0
|
||||
for record in self.ra_bill_ids:
|
||||
if record.qc_status != 'reject':
|
||||
hours = hours + record.hours
|
||||
if hours > self.hours:
|
||||
raise ValidationError(_("Hours should be less than total hours"))
|
||||
|
||||
@api.depends('total_cost', 'remaining_amount')
|
||||
def _compute_payment_progress(self):
|
||||
for rec in self:
|
||||
progress = 0.0
|
||||
if rec.total_cost and rec.remaining_amount:
|
||||
progress = (rec.remaining_amount * 100) / rec.total_cost
|
||||
rec.progress = 100 - progress
|
||||
|
||||
@api.depends('stage', 'ra_bill_ids')
|
||||
def compute_completion_date(self):
|
||||
for rec in self:
|
||||
date = None
|
||||
if rec.stage == "done":
|
||||
dates = rec.ra_bill_ids.mapped('date')
|
||||
if dates:
|
||||
date = dates[-1]
|
||||
rec.completion_date = date
|
||||
|
||||
|
||||
class LabourContractLine(models.Model):
|
||||
_name = 'labour.contract.line'
|
||||
_description = "Labour Contract Line"
|
||||
_rec_name = 'remark'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
contract_id = fields.Many2one('labour.subcontract', string="Labour Subcontract", ondelete='cascade')
|
||||
percentage = fields.Float(string="Percentage", tracking=True, compute="_compute_percentage")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
amount = fields.Monetary(tracking=True, string="Amount", compute="_compute_percentage_amount")
|
||||
date = fields.Date(string="Date", default=fields.Date.today(), tracking=True)
|
||||
remark = fields.Char(string="Remark", tracking=True)
|
||||
purchase_order_id = fields.Many2one('purchase.order', string="Purchase Order")
|
||||
bill_id = fields.Many2one('account.move', string="Bill")
|
||||
payment_state = fields.Selection(related="bill_id.payment_state", string="Payment State", tracking=True)
|
||||
state = fields.Selection(related="purchase_order_id.state", string="State", tracking=True)
|
||||
po_bill = fields.Selection(related="contract_id.po_bill")
|
||||
hours = fields.Float(string="Hours")
|
||||
retention_percentage = fields.Float(string="Retention(%)")
|
||||
retention_amount = fields.Monetary(string="Retention Amount", compute="compute_retention_amount")
|
||||
final_amount = fields.Monetary(string="Total Amount", compute="_compute_final_amount")
|
||||
|
||||
# QC Check
|
||||
qc_user_id = fields.Many2one('res.users', string="QC Responsible", tracking=True)
|
||||
qc_status = fields.Selection([('draft', 'Draft'),
|
||||
('request', 'Department Approval'),
|
||||
('approve', 'Approve'),
|
||||
('reject', 'Reject')],
|
||||
default='draft', string="Quality Check Status", tracking=True)
|
||||
reject_reason = fields.Text(string="Reject Reason", tracking=True)
|
||||
|
||||
def unlink(self):
|
||||
for rec in self:
|
||||
if rec.qc_status != 'draft':
|
||||
raise ValidationError(_("You can't delete until Quality Check status is in Draft"))
|
||||
else:
|
||||
return super(LabourContractLine, self).unlink()
|
||||
|
||||
@api.constrains('hours')
|
||||
def _check_ra_bill_hours(self):
|
||||
ra_bill_ids = self.env['labour.contract.line'].search([('contract_id', '=', self.contract_id.id)])
|
||||
hours = 0
|
||||
for record in ra_bill_ids:
|
||||
if record.qc_status != 'reject':
|
||||
hours = hours + record.hours
|
||||
if hours > self.contract_id.hours:
|
||||
raise ValidationError(_("Hours should be less than total hours"))
|
||||
|
||||
@api.depends('contract_id', 'hours', 'contract_id.tax_id')
|
||||
def _compute_percentage_amount(self):
|
||||
for rec in self:
|
||||
amount = 0.0
|
||||
if rec.hours and rec.contract_id:
|
||||
amount = (rec.contract_id.cost + (
|
||||
rec.contract_id.tax_id.amount * rec.contract_id.cost / 100)) * rec.hours
|
||||
rec.amount = amount
|
||||
|
||||
@api.depends('hours', 'retention_amount')
|
||||
def _compute_final_amount(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.amount:
|
||||
total = rec.amount - rec.retention_amount
|
||||
rec.final_amount = total
|
||||
|
||||
@api.depends('amount', 'retention_percentage')
|
||||
def compute_retention_amount(self):
|
||||
for rec in self:
|
||||
retention_amount = 0.0
|
||||
retention_amount = rec.amount * rec.retention_percentage / 100
|
||||
rec.retention_amount = retention_amount
|
||||
|
||||
@api.depends('contract_id', 'amount')
|
||||
def _compute_percentage(self):
|
||||
for rec in self:
|
||||
percentage = 0.0
|
||||
if rec.contract_id and rec.amount > 0:
|
||||
percentage = (100 * rec.amount) / rec.contract_id.total_cost
|
||||
rec.percentage = percentage
|
||||
|
||||
def action_quality_check(self):
|
||||
self.qc_status = 'request'
|
||||
|
||||
def action_quality_check_approve(self):
|
||||
self.qc_status = 'approve'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_quality_check_reject(self):
|
||||
self.qc_status = 'reject'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.qc_status = 'draft'
|
||||
|
||||
def action_create_ra_bill(self):
|
||||
if self.po_bill == 'bill':
|
||||
record = {
|
||||
'product_id': self.contract_id.product_id.id,
|
||||
'name': self.contract_id.name,
|
||||
'quantity': 1,
|
||||
'price_unit': self.final_amount,
|
||||
'tax_ids': False
|
||||
}
|
||||
invoice_lines = [(0, 0, record)]
|
||||
data = {
|
||||
'partner_id': self.contract_id.vendor_id.id,
|
||||
'invoice_date': self.date,
|
||||
'invoice_line_ids': invoice_lines,
|
||||
'move_type': 'in_invoice',
|
||||
'labour_subcontract_id': self.contract_id.id,
|
||||
'job_order_id': self.contract_id.job_order_id.id,
|
||||
'purchase_order': 'equipment'
|
||||
}
|
||||
invoice_id = self.env['account.move'].sudo().create(data)
|
||||
remaining_amount = self.contract_id.remaining_amount
|
||||
self.contract_id.remaining_amount = remaining_amount - self.amount
|
||||
self.bill_id = invoice_id.id
|
||||
qty = self.contract_id.remain_hours
|
||||
self.contract_id.remain_hours = qty - self.hours
|
||||
elif self.po_bill == 'purchase_order':
|
||||
purchase_record = {
|
||||
'product_id': self.contract_id.product_id.id,
|
||||
'name': self.contract_id.name
|
||||
,
|
||||
'product_qty': 1,
|
||||
'price_unit': self.final_amount,
|
||||
}
|
||||
purchase_lines = [(0, 0, purchase_record)]
|
||||
purchase_data = {
|
||||
'partner_id': self.contract_id.vendor_id.id,
|
||||
'order_line': purchase_lines,
|
||||
'job_order_id': self.contract_id.job_order_id.id,
|
||||
'labour_subcontract_id': self.contract_id.id,
|
||||
'purchase_order': 'equipment'
|
||||
}
|
||||
purchase_order_id = self.env['purchase.order'].create(purchase_data)
|
||||
self.purchase_order_id = purchase_order_id.id
|
||||
remaining_amount = self.contract_id.remaining_amount
|
||||
self.contract_id.remaining_amount = remaining_amount - self.amount
|
||||
qty = self.contract_id.remain_hours
|
||||
self.contract_id.remain_hours = qty - self.hours
|
||||
|
||||
|
||||
class OverheadSubcontract(models.Model):
|
||||
_name = 'overhead.subcontract'
|
||||
_description = "Overhead Subcontract"
|
||||
_rec_name = 'seq'
|
||||
|
||||
seq = fields.Char(string='Sequence', required=True, readonly=True, default=lambda self: _('New'))
|
||||
name = fields.Char(string="Title")
|
||||
product_id = fields.Many2one('product.product', string="Product", domain="[('is_overhead','=',True)]")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
uom_id = fields.Many2one(related="product_id.uom_po_id", string="Unit of Measure")
|
||||
cost = fields.Monetary(string="Cost / Unit")
|
||||
po_bill = fields.Selection([('bill', 'Bill'), ('purchase_order', 'Purchase Order')], string="Type", default='bill')
|
||||
stage = fields.Selection([('draft', 'Draft'), ('in_progress', 'In Progress'), ('done', 'Done')], default='draft')
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
job_type_id = fields.Many2one('job.type', string="Work Type")
|
||||
sub_category_id = fields.Many2one('job.sub.category', string="Work Sub Type")
|
||||
ra_bill_ids = fields.One2many('overhead.contract.line', 'contract_id', string="Ra Bills")
|
||||
|
||||
# Project Details
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
phase_id = fields.Many2one(related="job_order_id.job_sheet_id", string="Project Phase(WBS)", store=True)
|
||||
project_id = fields.Many2one(related='job_order_id.project_id', string="Sub Project", store=True)
|
||||
task_id = fields.Many2one(related="job_order_id.task_id", string="Task", store=True)
|
||||
# Department
|
||||
department_id = fields.Many2one(related="job_order_id.department_id", string="Department", store=True)
|
||||
manager_ids = fields.Many2many('res.users', store=True, string="Manager")
|
||||
user_id = fields.Many2one(related="job_order_id.user_id", string="Responsible")
|
||||
|
||||
# Calculation
|
||||
qty = fields.Integer(string="Qty.", default=1)
|
||||
remain_qty = fields.Integer(string="Remaining Qty")
|
||||
total_cost = fields.Monetary(string="Total Cost")
|
||||
remaining_amount = fields.Monetary(string="Remaining Amount")
|
||||
progress = fields.Float(string="Completed Billing", compute="_compute_payment_progress")
|
||||
tax_id = fields.Many2one('account.tax', string='Tax')
|
||||
completion_date = fields.Date(string="Completion Date", compute="compute_completion_date")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('seq', _('New')) == _('New'):
|
||||
vals['seq'] = self.env['ir.sequence'].next_by_code('overhead.sub') or _('New')
|
||||
res = super(OverheadSubcontract, self).create(vals_list)
|
||||
return res
|
||||
|
||||
def action_in_progress(self):
|
||||
self.stage = 'in_progress'
|
||||
|
||||
def action_state_done(self):
|
||||
self.stage = 'done'
|
||||
|
||||
@api.constrains('ra_bill_ids', 'qty')
|
||||
def _check_ra_bill_qty(self):
|
||||
qty = 0
|
||||
for record in self.ra_bill_ids:
|
||||
if record.qc_status != 'reject':
|
||||
qty = qty + record.qty
|
||||
if qty > self.qty:
|
||||
raise ValidationError(_("Quantity should be less than total qty."))
|
||||
|
||||
@api.depends('total_cost', 'remaining_amount')
|
||||
def _compute_payment_progress(self):
|
||||
for rec in self:
|
||||
progress = 0.0
|
||||
if rec.total_cost and rec.remaining_amount:
|
||||
progress = (rec.remaining_amount * 100) / rec.total_cost
|
||||
rec.progress = 100 - progress
|
||||
|
||||
@api.depends('stage', 'ra_bill_ids')
|
||||
def compute_completion_date(self):
|
||||
for rec in self:
|
||||
date = None
|
||||
if rec.stage == "done":
|
||||
dates = rec.ra_bill_ids.mapped('date')
|
||||
if dates:
|
||||
date = dates[-1]
|
||||
rec.completion_date = date
|
||||
|
||||
|
||||
class OverheadContractLine(models.Model):
|
||||
_name = 'overhead.contract.line'
|
||||
_description = "Overhead Contract Line"
|
||||
_rec_name = 'remark'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
contract_id = fields.Many2one('overhead.subcontract', string="Overhead Subcontract", ondelete='cascade')
|
||||
percentage = fields.Float(string="Percentage", tracking=True, compute="_compute_percentage")
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
amount = fields.Monetary(tracking=True, string="Amount", compute="_compute_percentage_amount")
|
||||
date = fields.Date(string="Date", default=fields.Date.today(), tracking=True)
|
||||
remark = fields.Char(string="Remark", tracking=True)
|
||||
purchase_order_id = fields.Many2one('purchase.order', string="Purchase Order")
|
||||
bill_id = fields.Many2one('account.move', string="Bill")
|
||||
payment_state = fields.Selection(related="bill_id.payment_state", string="Payment State", tracking=True)
|
||||
state = fields.Selection(related="purchase_order_id.state", string="State", tracking=True)
|
||||
po_bill = fields.Selection(related="contract_id.po_bill")
|
||||
qty = fields.Integer(string="Qty")
|
||||
retention_percentage = fields.Float(string="Retention(%)")
|
||||
retention_amount = fields.Monetary(string="Retention Amount", compute="compute_retention_amount")
|
||||
final_amount = fields.Monetary(string="Total Amount", compute="_compute_final_amount")
|
||||
|
||||
# QC Check
|
||||
qc_user_id = fields.Many2one('res.users', string="QC Responsible", tracking=True)
|
||||
qc_status = fields.Selection([('draft', 'Draft'),
|
||||
('request', 'Department Approval'),
|
||||
('approve', 'Approve'),
|
||||
('reject', 'Reject')], default='draft', string="Quality Check Status", tracking=True)
|
||||
reject_reason = fields.Text(string="Reject Reason", tracking=True)
|
||||
|
||||
def unlink(self):
|
||||
for rec in self:
|
||||
if rec.qc_status != 'draft':
|
||||
raise ValidationError(_("You can't delete until Quality Check status is in Draft"))
|
||||
else:
|
||||
return super(OverheadContractLine, self).unlink()
|
||||
|
||||
@api.constrains('qty')
|
||||
def _check_ra_bill_qty(self):
|
||||
ra_bill_ids = self.env['overhead.contract.line'].search([('contract_id', '=', self.contract_id.id)])
|
||||
qty = 0
|
||||
for record in ra_bill_ids:
|
||||
if record.qc_status != 'reject':
|
||||
qty = qty + record.qty
|
||||
if qty > self.contract_id.qty:
|
||||
raise ValidationError(_("Quantity should be less than total qty."))
|
||||
|
||||
@api.depends('contract_id', 'qty', 'contract_id.tax_id')
|
||||
def _compute_percentage_amount(self):
|
||||
for rec in self:
|
||||
amount = 0.0
|
||||
if rec.qty and rec.contract_id:
|
||||
amount = (rec.contract_id.cost + (rec.contract_id.tax_id.amount * rec.contract_id.cost / 100)) * rec.qty
|
||||
rec.amount = amount
|
||||
|
||||
@api.depends('qty', 'retention_amount')
|
||||
def _compute_final_amount(self):
|
||||
for rec in self:
|
||||
total = 0.0
|
||||
if rec.amount:
|
||||
total = rec.amount - rec.retention_amount
|
||||
rec.final_amount = total
|
||||
|
||||
@api.depends('amount', 'retention_percentage')
|
||||
def compute_retention_amount(self):
|
||||
for rec in self:
|
||||
retention_amount = 0.0
|
||||
if rec.retention_percentage:
|
||||
retention_amount = rec.amount * rec.retention_percentage / 100
|
||||
rec.retention_amount = retention_amount
|
||||
|
||||
@api.depends('contract_id', 'amount')
|
||||
def _compute_percentage(self):
|
||||
for rec in self:
|
||||
percentage = 0.0
|
||||
if rec.contract_id and rec.amount > 0:
|
||||
percentage = (100 * rec.amount) / rec.contract_id.total_cost
|
||||
rec.percentage = percentage
|
||||
|
||||
def action_quality_check(self):
|
||||
self.qc_status = 'request'
|
||||
|
||||
def action_quality_check_approve(self):
|
||||
self.qc_status = 'approve'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_quality_check_reject(self):
|
||||
self.qc_status = 'reject'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.qc_status = 'draft'
|
||||
|
||||
def action_create_ra_bill(self):
|
||||
if self.po_bill == 'bill':
|
||||
record = {
|
||||
'product_id': self.contract_id.product_id.id,
|
||||
'name': self.contract_id.name,
|
||||
'quantity': 1,
|
||||
'price_unit': self.final_amount,
|
||||
'tax_ids': False
|
||||
}
|
||||
invoice_lines = [(0, 0, record)]
|
||||
data = {
|
||||
'partner_id': self.contract_id.vendor_id.id,
|
||||
'invoice_date': self.date,
|
||||
'invoice_line_ids': invoice_lines,
|
||||
'move_type': 'in_invoice',
|
||||
'overhead_subcontract_id': self.contract_id.id,
|
||||
'job_order_id': self.contract_id.job_order_id.id,
|
||||
'purchase_order': 'overhead'
|
||||
}
|
||||
invoice_id = self.env['account.move'].sudo().create(data)
|
||||
remaining_amount = self.contract_id.remaining_amount
|
||||
self.contract_id.remaining_amount = remaining_amount - self.amount
|
||||
self.bill_id = invoice_id.id
|
||||
qty = self.contract_id.remain_qty
|
||||
self.contract_id.remain_qty = qty - self.qty
|
||||
elif self.po_bill == 'purchase_order':
|
||||
purchase_record = {
|
||||
'product_id': self.contract_id.product_id.id,
|
||||
'name': self.contract_id.name
|
||||
,
|
||||
'product_qty': 1,
|
||||
'price_unit': self.final_amount,
|
||||
}
|
||||
purchase_lines = [(0, 0, purchase_record)]
|
||||
purchase_data = {
|
||||
'partner_id': self.contract_id.vendor_id.id,
|
||||
'order_line': purchase_lines,
|
||||
'job_order_id': self.contract_id.job_order_id.id,
|
||||
'overhead_subcontract_id': self.contract_id.id,
|
||||
'purchase_order': 'overhead'
|
||||
}
|
||||
purchase_order_id = self.env['purchase.order'].create(
|
||||
purchase_data)
|
||||
self.purchase_order_id = purchase_order_id.id
|
||||
remaining_amount = self.contract_id.remaining_amount
|
||||
self.contract_id.remaining_amount = remaining_amount - self.amount
|
||||
qty = self.contract_id.remain_qty
|
||||
self.contract_id.remain_qty = qty - self.qty
|
||||
|
||||
|
||||
class MaterialConsume(models.Model):
|
||||
_name = 'material.consume'
|
||||
_description = "Material Consume Order"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_rec_name = 'seq'
|
||||
|
||||
seq = fields.Char(string='Sequence', required=True, readonly=True, default=lambda self: _('New'))
|
||||
date = fields.Date(string="Date", default=fields.Date.today())
|
||||
remark = fields.Char(string="Remark")
|
||||
|
||||
warehouse_id = fields.Many2one('stock.warehouse')
|
||||
consume_order_id = fields.Many2one('stock.picking', string="Consume Order")
|
||||
state = fields.Selection(related="consume_order_id.state", string="Status")
|
||||
consume_order_ids = fields.One2many('material.consume.line', 'material_consume_id', string="Material Consume")
|
||||
|
||||
# Project Details
|
||||
job_order_id = fields.Many2one('job.order', string="Work Order")
|
||||
phase_id = fields.Many2one(related="job_order_id.job_sheet_id", string="Project Phase(WBS)", store=True)
|
||||
project_id = fields.Many2one(related='job_order_id.project_id', string="Sub Project", store=True)
|
||||
task_id = fields.Many2one(related="job_order_id.task_id", string="Task", store=True)
|
||||
# Department
|
||||
department_id = fields.Many2one(related="job_order_id.department_id", string="Department", store=True)
|
||||
manager_ids = fields.Many2many('res.users', store=True, string="Manager")
|
||||
user_id = fields.Many2one(related="job_order_id.user_id", string="Responsible", store=True)
|
||||
|
||||
# Quality Check
|
||||
qc_user_id = fields.Many2one('res.users', string="QC Responsible", tracking=True)
|
||||
qc_status = fields.Selection([('draft', 'Draft'), ('request', 'Department Approval'), ('approve', 'Approve'),
|
||||
('reject', 'Reject'), ('cancel', 'Cancel')], default='draft',
|
||||
string="Quality Check Status", tracking=True)
|
||||
reject_reason = fields.Text(string="Reject Reason", tracking=True)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('seq', _('New')) == _('New'):
|
||||
vals['seq'] = self.env['ir.sequence'].next_by_code('material.consume') or _('New')
|
||||
res = super(MaterialConsume, self).create(vals_list)
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
if self.qc_status != 'draft':
|
||||
raise ValidationError(_("You can't delete until Quality Check status is in Draft"))
|
||||
else:
|
||||
return super(MaterialConsume, self).unlink()
|
||||
|
||||
@api.constrains('consume_order_ids')
|
||||
def _check_material_line_qty(self):
|
||||
for rec in self.consume_order_ids:
|
||||
if rec.qty > rec.material_line_id.remain_qty:
|
||||
raise ValidationError(_("Qty should be less than remain Qty."))
|
||||
|
||||
def action_quality_check(self):
|
||||
self.qc_status = 'request'
|
||||
|
||||
def action_quality_check_approve(self):
|
||||
self.qc_status = 'approve'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_quality_check_reject(self):
|
||||
self.qc_status = 'reject'
|
||||
self.qc_user_id = self.env.user.id
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.qc_status = 'draft'
|
||||
|
||||
def action_cancel_consume_order(self):
|
||||
self.qc_status = 'cancel'
|
||||
|
||||
def action_create_consume_order(self):
|
||||
dest_location_id = False
|
||||
if self.warehouse_id.consume_stock_location_id:
|
||||
dest_location_id = self.warehouse_id.consume_stock_location_id
|
||||
else:
|
||||
dest_location_id = self.env['stock.location'].create(
|
||||
{'name': "Consume Location/" + str(self.warehouse_id.name), 'usage': 'production'})
|
||||
self.warehouse_id.consume_stock_location_id = dest_location_id.id
|
||||
lines = []
|
||||
for rec in self.consume_order_ids:
|
||||
lines.append((0, 0, {
|
||||
'product_id': rec.material_id.id,
|
||||
'product_uom_qty': rec.qty,
|
||||
'product_uom': rec.uom_id.id,
|
||||
'location_id': self.warehouse_id.lot_stock_id.id,
|
||||
'location_dest_id': dest_location_id.id,
|
||||
'name': rec.name
|
||||
}))
|
||||
source_id = self.warehouse_id.lot_stock_id
|
||||
stock_picking_type_id = self.env['stock.picking.type'].search(
|
||||
[('code', '=', 'outgoing'), ('warehouse_id', '=', self.warehouse_id.id)], limit=1)
|
||||
delivery_record = {
|
||||
'picking_type_id': stock_picking_type_id.id,
|
||||
'location_id': source_id.id,
|
||||
'location_dest_id': dest_location_id.id,
|
||||
'move_ids_without_package': lines,
|
||||
'consume_order_id': self.job_order_id.id,
|
||||
'material_consume_id': self.id,
|
||||
'move_type': 'one'
|
||||
}
|
||||
delivery_id = self.env['stock.picking'].create(delivery_record)
|
||||
self.consume_order_id = delivery_id.id
|
||||
for rec in self.consume_order_ids:
|
||||
remain_qty = rec.material_line_id.remain_qty
|
||||
usage_qty = rec.material_line_id.usage_qty
|
||||
rec.material_line_id.remain_qty = remain_qty - rec.qty
|
||||
rec.material_line_id.usage_qty = usage_qty + rec.qty
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Consume Order'),
|
||||
'res_model': 'stock.picking',
|
||||
'res_id': delivery_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
|
||||
|
||||
class MaterialConsumeLine(models.Model):
|
||||
_name = 'material.consume.line'
|
||||
_description = "Material Consume Line"
|
||||
|
||||
material_id = fields.Many2one('product.product', string="Material", domain="[('is_material','=',True)]")
|
||||
uom_id = fields.Many2one(related="material_id.uom_id", string="UOM")
|
||||
name = fields.Char(string="Description")
|
||||
qty = fields.Integer(string="Qty")
|
||||
material_consume_id = fields.Many2one('material.consume', string="Material Consume")
|
||||
qc_status = fields.Selection(related="material_consume_id.qc_status")
|
||||
material_line_id = fields.Many2one('order.material.line')
|
||||
Reference in New Issue
Block a user