Tower: upload tk_construction_management 18.0.2.0.8 (was 18.0.2.0.8, via marketplace)

This commit is contained in:
2026-05-08 19:20:12 +00:00
parent c412640ca2
commit 63c62699f5
174 changed files with 44759 additions and 0 deletions

View 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

View 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'
}

View 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

View 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

View 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")

View File

@@ -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

View File

@@ -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"

View 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

View 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

View 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'

View 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 ConstructionResUser(models.Model):
_inherit = 'res.users'
department_ids = fields.Many2many('construction.department', string="Department ")

View 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

View 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

View 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

View 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

View 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

View 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")

View 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

View 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')

View 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")

View 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')