782 lines
38 KiB
Python
Executable File
782 lines
38 KiB
Python
Executable File
from odoo import api, fields, models, _
|
|
import logging
|
|
_logger = logging.getLogger(__name__)
|
|
from odoo.exceptions import ValidationError
|
|
from datetime import datetime,date,timedelta
|
|
from odoo.exceptions import UserError
|
|
import io
|
|
import xlsxwriter
|
|
from odoo.tools.misc import xlsxwriter
|
|
import base64
|
|
|
|
class SOS_FG_Plan(models.Model):
|
|
_name = 'sos_fg_plan'
|
|
_description = 'FG Plan'
|
|
_rec_name = 'plan_ref_no'
|
|
|
|
|
|
plan_ref_no = fields.Char(
|
|
readonly=True, required=True, string='Plan ID',
|
|
default=lambda self: self._generate_id()
|
|
)
|
|
line_ids = fields.One2many('sos_fg_plan_line', 'plan_id', string='Lines', ondelete='cascade')
|
|
sfg_line_ids = fields.One2many('sos_sfg_plan_line', 'plan_id', string='SFG Lines', ondelete='cascade')
|
|
material_line_ids = fields.One2many('sos_material_plan_line', 'plan_id', string='Lines', ondelete='cascade')
|
|
|
|
approved_by = fields.Many2one('res.users', string='Approved By')
|
|
approval_image = fields.Image(related="approved_by.signature_image",string='Top Management Approval Sign',readonly=True)
|
|
approved_on = fields.Datetime(string="Approved On")
|
|
|
|
prepared_by = fields.Many2one('res.users', string='Planned By')
|
|
prepared_image = fields.Image(related="prepared_by.signature_image",string='Prepared By Sign',readonly=True)
|
|
prepared_on = fields.Datetime(string="Planned On")
|
|
blowup = fields.Boolean(string="Blowup Done",default=False)
|
|
target_date = fields.Date(string="Target Date",required=True)
|
|
indent_start_date = fields.Date(string="Indent Start Date", default=lambda self: fields.Date.context_today(self))
|
|
indent_status = fields.Selection([('open', 'Open'), ('cancel', 'Cancel'), ('hold', 'Hold'), ('close', 'Closed')], string='Indent Status', default="open")
|
|
hold_cancel_reason = fields.Text(string='Hold/Cancel Reason')
|
|
hold_cancel_by = fields.Many2one('res.users', string='Hold & Cancelled By')
|
|
company_id = fields.Many2one('res.company', store=True, copy=False,
|
|
string="Company",
|
|
default=lambda self: self.env.user.company_id.id)
|
|
currency_id = fields.Many2one('res.currency', string="Currency",
|
|
related='company_id.currency_id',
|
|
default=lambda
|
|
self: self.env.user.company_id.currency_id.id)
|
|
estimated_assembling_charges = fields.Monetary(compute='_compute_assembling_charges', string="SFG Assembling Charges", currency_field='currency_id', readonly=True)
|
|
estimated_material_cost = fields.Monetary(compute='_compute_material_charges', string="Material Procurement Cost", currency_field='currency_id', readonly=True)
|
|
other_charges = fields.Monetary(string="Transport & Other Charges",currency_field='currency_id')
|
|
overall_total = fields.Monetary(compute='_compute_overall_total',string ="Overall Estimated Cost",currency_field='currency_id')
|
|
tax_value = fields.Monetary(string="Tax(18%)")
|
|
@api.depends('estimated_assembling_charges', 'estimated_material_cost', 'other_charges')
|
|
def _compute_overall_total(self):
|
|
for record in self:
|
|
overall_total = (
|
|
(record.estimated_assembling_charges or 0) +
|
|
(record.estimated_material_cost or 0) +
|
|
(record.other_charges or 0)
|
|
)
|
|
|
|
with_tax = overall_total * (1.18)
|
|
record.tax_value = with_tax - overall_total
|
|
record.overall_total = with_tax
|
|
@api.depends('sfg_line_ids.total_assembling_cost')
|
|
def _compute_assembling_charges(self):
|
|
for record in self:
|
|
record.estimated_assembling_charges = round(sum(line.total_assembling_cost for line in record.sfg_line_ids), 2)
|
|
@api.depends('material_line_ids.total_approx_price')
|
|
def _compute_material_charges(self):
|
|
for record in self:
|
|
record.estimated_material_cost = round(sum(line.total_approx_price for line in record.material_line_ids), 2)
|
|
|
|
def open_reason_wizard(self):
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'hold_cancel_reason_wizard',
|
|
'name':'Indent Status Change',
|
|
'view_mode': 'form',
|
|
'target': 'new',
|
|
'context': {
|
|
'default_model_id': self.id,
|
|
}
|
|
}
|
|
|
|
|
|
@api.onchange('target_date', 'indent_start_date')
|
|
def _onchange_dates(self):
|
|
for record in self:
|
|
if record.target_date:
|
|
max_target_date = date.today() + timedelta(days=90)
|
|
if record.target_date > max_target_date:
|
|
raise UserError("Target Date cannot exceed 90 days from the Indent Start Date(Today)")
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
res = super(SOS_FG_Plan, self).default_get(fields_list)
|
|
if 'line_ids' in fields_list and 'line_ids' not in res:
|
|
default_lines = self._get_default_lines('sos_fg','fg_name')
|
|
res['line_ids'] = [(0, 0, line) for line in default_lines]
|
|
if 'sfg_line_ids' in fields_list and 'sfg_line_ids' not in res:
|
|
default_lines = self._get_default_lines('sos_sfg','sfg_name')
|
|
res['sfg_line_ids'] = [(0, 0, line) for line in default_lines]
|
|
if 'material_line_ids' in fields_list and 'material_line_ids' not in res:
|
|
default_lines = self._get_default_lines('sos_material','material_name')
|
|
res['material_line_ids'] = [(0, 0, line) for line in default_lines]
|
|
return res
|
|
|
|
def _generate_id(self):
|
|
model_name = 'sos_fg_plan'
|
|
form_name = 'INDENT'
|
|
field_name = 'plan_ref_no'
|
|
today = fields.Date.today()
|
|
year_start = today.year if today.month > 3 else today.year - 1
|
|
year_end = year_start + 1
|
|
fy = f"{year_start % 100}-{year_end % 100}"
|
|
|
|
month = today.strftime('%m')
|
|
base_sequence_prefix = f"SOS/{form_name}/{fy}/{month}/"
|
|
|
|
records = self.env[model_name].sudo().search(
|
|
[(field_name, 'like', f"{base_sequence_prefix}%")],
|
|
order=f"{field_name} desc",
|
|
limit=1
|
|
)
|
|
|
|
if records:
|
|
last_sequence = records[0][field_name]
|
|
last_suffix = last_sequence.split('/')[-1]
|
|
if last_suffix.isdigit():
|
|
new_suffix = f"{last_suffix}a"
|
|
else:
|
|
base_num = last_suffix[:-1]
|
|
last_alpha = last_suffix[-1]
|
|
next_alpha = chr(ord(last_alpha) + 1) if last_alpha < 'z' else 'a'
|
|
new_suffix = f"{base_num}{next_alpha}"
|
|
else:
|
|
new_suffix = '016'
|
|
|
|
return f"{base_sequence_prefix}{new_suffix}"
|
|
|
|
|
|
|
|
|
|
def _get_default_lines(self,model,column):
|
|
products = self.env[model].search([])
|
|
default_lines = []
|
|
for product in products:
|
|
if (product.order_qty + product.minimum_stock_qty) > product.inhand_stock_qty:
|
|
to_be_produce = (product.order_qty + product.minimum_stock_qty) - (product.inhand_stock_qty + product.in_transit_stock_qty)
|
|
if to_be_produce > product.minimum_order_qty:
|
|
final_qty = to_be_produce
|
|
else:
|
|
final_qty = product.minimum_order_qty
|
|
default_line = {
|
|
column: product.id,
|
|
'inhand_qty': product.inhand_stock_qty,
|
|
'in_transit_stock_qty': product.in_transit_stock_qty,
|
|
'minimum_stock_qty': product.minimum_stock_qty,
|
|
'required_qty': product.order_qty,
|
|
'minimum_order_qty':product.minimum_order_qty,
|
|
'approved_cnt':final_qty,
|
|
'actual_required_qty':to_be_produce
|
|
}
|
|
if column == "material_name":
|
|
default_line['approx_price'] = product.unit_price
|
|
elif column == "sfg_name":
|
|
default_line['assembling_cost'] = product.assembling_charges
|
|
default_lines.append(default_line)
|
|
return default_lines
|
|
|
|
|
|
|
|
def send_indent_plan_email(self, email_ids):
|
|
template = self.env.ref('sos_inventory.send_indent_plan_email_template')
|
|
if template:
|
|
template.email_to = ','.join(email_ids)
|
|
template.send_mail(self.id, force_send=True)
|
|
def get_unique_emails(self):
|
|
group_refs = [
|
|
'sos_inventory.sos_scg_group_user',
|
|
'sos_inventory.sos_scg_group_manager',
|
|
'sos_inventory.sos_finance_user',
|
|
'sos_inventory.sos_management_user',
|
|
'sos_inventory.sos_qc_user',
|
|
'sos_inventory.sos_qa_user',
|
|
'sos_inventory.sos_production_user'
|
|
]
|
|
group_ids = [self.env.ref(group_ref).id for group_ref in group_refs]
|
|
users = self.env['res.users'].search([('groups_id', 'in', group_ids)])
|
|
emails = list(set(users.mapped('email')))
|
|
return emails
|
|
def action_export_final(self):
|
|
output = io.BytesIO()
|
|
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
|
|
|
|
# Define custom headers for each sheet
|
|
line_headers = {
|
|
'fg_name': 'FG Name',
|
|
'approved_cnt': 'Planned Qty'
|
|
}
|
|
|
|
sfg_headers = {
|
|
'sfg_name': 'SFG Name',
|
|
'actual_required_qty': 'Actual Req Qty ',
|
|
'minimum_order_qty': 'Minimum Order Qty',
|
|
'assembling_cost':'Assembling Cost per unit',
|
|
'total_assembling_cost':'Total Assembling Cost',
|
|
'approved_cnt':'Planned Qty'
|
|
}
|
|
|
|
material_headers = {
|
|
'material_name': 'Material Name',
|
|
'approx_price': 'Unit Price',
|
|
'actual_required_qty': 'Field Z Header',
|
|
'minimum_order_qty':'Minimum Order Qty',
|
|
'total_approx_price':'Approx Price',
|
|
'approved_cnt':'Planned Qty'
|
|
}
|
|
|
|
custom_headers = {
|
|
'estimated_assembling_charges': 'SFG Assembling Charges',
|
|
'estimated_material_cost': 'Material Procurement Cost',
|
|
'other_charges': 'Transport & Other Charges',
|
|
'tax_value':'Tax (18%)',
|
|
'overall_total':'Overall Estimated Cost'
|
|
}
|
|
|
|
self._write_sheet(workbook, 'FG Plan', self.line_ids, line_headers)
|
|
self._write_sheet(workbook, 'SFG Plan', self.sfg_line_ids, sfg_headers)
|
|
self._write_sheet(workbook, 'Material Plan', self.material_line_ids, material_headers)
|
|
|
|
# Write the custom sheet with fields from the main model (self) and custom headers
|
|
self._write_custom_sheet(workbook, 'Budget', custom_headers)
|
|
|
|
workbook.close()
|
|
output.seek(0)
|
|
|
|
# Create attachment and initiate download
|
|
attachment = self.env['ir.attachment'].create({
|
|
'name': f'{self.plan_ref_no}_Export.xlsx',
|
|
'type': 'binary',
|
|
'datas': base64.b64encode(output.read()),
|
|
'res_model': self._name,
|
|
'res_id': self.id,
|
|
'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
})
|
|
|
|
return {
|
|
'type': 'ir.actions.act_url',
|
|
'url': f'/web/content/{attachment.id}?download=true',
|
|
'target': 'self',
|
|
}
|
|
|
|
def _write_sheet(self, workbook, sheet_name, line_records, headers):
|
|
"""Write data to each sheet based on specified fields and custom headers."""
|
|
worksheet = workbook.add_worksheet(sheet_name)
|
|
|
|
# Write custom headers
|
|
for col, header in enumerate(headers.values()):
|
|
worksheet.write(0, col, header)
|
|
|
|
# Write data rows
|
|
for row, record in enumerate(line_records, start=1):
|
|
for col, field in enumerate(headers.keys()):
|
|
value = getattr(record, field, '')
|
|
# Convert value if necessary (Many2one, Date, etc.)
|
|
if isinstance(value, models.Model):
|
|
worksheet.write(row, col, value.display_name if value else '')
|
|
elif isinstance(value, (fields.Date, fields.Datetime)):
|
|
worksheet.write(row, col, value.strftime('%Y-%m-%d') if value else '')
|
|
else:
|
|
worksheet.write(row, col, value)
|
|
|
|
def _write_custom_sheet(self, workbook, sheet_name, headers):
|
|
"""Write a custom sheet with fields from the main model, row by row with custom headers."""
|
|
worksheet = workbook.add_worksheet(sheet_name)
|
|
|
|
# Write each field as a separate row with its custom header and value
|
|
for row, (field, header) in enumerate(headers.items()):
|
|
# Write the header in the first column
|
|
worksheet.write(row, 0, header)
|
|
|
|
# Retrieve the value for the field and write it in the second column
|
|
value = getattr(self, field, '')
|
|
|
|
# Convert value if necessary (Many2one, Date, etc.)
|
|
if isinstance(value, models.Model):
|
|
worksheet.write(row, 1, value.display_name if value else '')
|
|
elif isinstance(value, (fields.Date, fields.Datetime)):
|
|
worksheet.write(row, 1, value.strftime('%Y-%m-%d') if value else '')
|
|
else:
|
|
worksheet.write(row, 1, value)
|
|
|
|
|
|
def action_top_approver_esign_btn(self):
|
|
sequence_util = self.env['sos_common_scripts']
|
|
result = sequence_util.action_assign_signature(
|
|
self,
|
|
'approved_by',
|
|
'approved_on',
|
|
'sos_inventory.sos_management_user'
|
|
)
|
|
|
|
email_addresses = self.get_unique_emails()
|
|
self.send_indent_plan_email(email_addresses)
|
|
self.env['sos_audit_log'].create_log('Approval', f'Indent <b>{self.plan_ref_no}</b> approved by {self.env.user.name}')
|
|
self.update_store_qty(self.line_ids,self.sfg_line_ids,self.material_line_ids)
|
|
self.create_production_plan(self.line_ids)
|
|
self.create_procurement_plan(self.material_line_ids)
|
|
self.create_fptc(self.line_ids)
|
|
self.create_sfg_quotation(self.sfg_line_ids)
|
|
|
|
def action_report_esign_btn(self):
|
|
if self.blowup == False:
|
|
self.blowup = True
|
|
missing_boms = []
|
|
for item in self.line_ids:
|
|
if item.approved_cnt != 0:
|
|
fg_name = item.fg_name.id
|
|
product_bom = self.env['sos_fg_bom'].search([
|
|
('fg_name', '=', fg_name),
|
|
('is_primary', '=', True)
|
|
], limit=1)
|
|
if not product_bom:
|
|
missing_boms.append(item.fg_name.name)
|
|
|
|
if missing_boms:
|
|
raise UserError(f"BOM for following product(s) are Missing, Delete those for this Indent or add BOM(s) : {', '.join(missing_boms)}")
|
|
else:
|
|
self.create_sfg_plan(self.line_ids)
|
|
self.create_material_plan(self.sfg_line_ids)
|
|
else:
|
|
raise UserError("Already Blowed up")
|
|
def action_approve_esign_btn(self):
|
|
body_html = f"""
|
|
<p>Below <b>Indent Budget</b> is waiting for your Approval</p>
|
|
"""
|
|
|
|
send_email = self.env['sos_common_scripts']
|
|
send_email.send_direct_email(self.env,"sos_fg_plan",self.id,"ramachandran.r@sosaley.in","Indent Budget Approval",body_html)
|
|
sequence_util = self.env['sos_common_scripts']
|
|
result = sequence_util.action_assign_signature(
|
|
self,
|
|
'prepared_by',
|
|
'prepared_on'
|
|
)
|
|
|
|
|
|
|
|
def update_store_qty(self,line_ids,sfg_line_ids,material_line_ids):
|
|
model_names = ['sos_fg', 'sos_sfg', 'sos_material']
|
|
|
|
for model_name in model_names:
|
|
if model_name == 'sos_fg':
|
|
column_name = 'fg_name'
|
|
line_ids_final = line_ids
|
|
elif model_name == 'sos_sfg':
|
|
column_name = 'sfg_name'
|
|
line_ids_final = sfg_line_ids
|
|
else:
|
|
column_name = 'material_name'
|
|
line_ids_final = material_line_ids
|
|
for item in line_ids_final:
|
|
f_id = getattr(item, column_name).id
|
|
|
|
sos_record = self.env[model_name].search([('id', '=', f_id)], limit=1)
|
|
if sos_record:
|
|
new_in_transit_qty = sos_record.in_transit_stock_qty + item.approved_cnt
|
|
|
|
sos_record.write({
|
|
'order_qty': 0,
|
|
'in_transit_stock_qty': new_in_transit_qty,
|
|
})
|
|
if model_name != "sos_fg":
|
|
sos_record.write({
|
|
'blocked_qty': sos_record.blocked_qty + item.approved_cnt
|
|
})
|
|
|
|
|
|
|
|
|
|
def create_sfg_quotation(self, line_ids):
|
|
quotation_model = self.env['sos_sfg_quote_generation']
|
|
quotation_line_model = self.env['sos_sfg_quote_generation_line']
|
|
quotation_record = quotation_model.search([('plan_ref_no', '=', self.plan_ref_no)], limit=1)
|
|
if not quotation_record:
|
|
quotation_record = quotation_model.create({'plan_ref_no': self.plan_ref_no})
|
|
for item in line_ids:
|
|
if item.approved_cnt != 0:
|
|
if item.blowup_action == "yes":
|
|
blowup_action_result = "with_materials"
|
|
else:
|
|
blowup_action_result = "without_materials"
|
|
quotation_line_model.create({
|
|
'material_name': item.sfg_name.id,
|
|
'required_qty': item.approved_cnt,
|
|
'inprogress_qty': item.approved_cnt,
|
|
'plan_id': quotation_record.id,
|
|
'purchase_type':blowup_action_result,
|
|
'supplier_name':item.sfg_name.service_providers
|
|
})
|
|
|
|
def create_procurement_plan(self, line_ids):
|
|
quotation_model = self.env['sos_quote_generation']
|
|
quotation_line_model = self.env['sos_quote_generation_line']
|
|
quotation_record = quotation_model.search([('plan_ref_no', '=', self.plan_ref_no)], limit=1)
|
|
if not quotation_record:
|
|
quotation_record = quotation_model.create({'plan_ref_no': self.plan_ref_no})
|
|
for item in line_ids:
|
|
if item.approved_cnt != 0:
|
|
quotation_line_model.create({
|
|
'material_name': item.material_name.id,
|
|
'required_qty': item.approved_cnt,
|
|
'plan_id': quotation_record.id,
|
|
'supplier_name':item.material_name.suppliers
|
|
})
|
|
def create_material_plan(self, line_ids):
|
|
material_stores_model = self.env['sos_material']
|
|
material_line_model = self.env['sos_material_plan_line']
|
|
|
|
# Dictionary to store component quantities
|
|
part_no_quantities = {}
|
|
|
|
# Process each line to populate the quantities
|
|
for item in line_ids:
|
|
if item.approved_cnt != 0 and item.blowup_action != 'no':
|
|
# Fetch the SFG BOM for the current item
|
|
sfg_bom = self.env['sos_sfg_bom'].search([
|
|
('name', '=', item.sfg_name.id)
|
|
], limit=1)
|
|
if sfg_bom:
|
|
# Fetch SFG BOM lines
|
|
sfg_lines = self.env['sos_sfg_bom_line'].search([('bom_id', '=', sfg_bom.id)])
|
|
for sfg_line in sfg_lines:
|
|
component = sfg_line.primary_component_id
|
|
if component:
|
|
req_qty = sfg_line.quantity * item.approved_cnt
|
|
|
|
# Aggregate quantities for the component
|
|
if component.part_no in part_no_quantities:
|
|
part_no_quantities[component.part_no]['req_qty'] += req_qty
|
|
else:
|
|
part_no_quantities[component.part_no] = {
|
|
'inhand_stock_qty': max(0, component.inhand_stock_qty - component.blocked_qty),
|
|
'in_transit_stock_qty': component.in_transit_stock_qty,
|
|
'minimum_stock_qty': component.minimum_stock_qty,
|
|
'minimum_order_qty': component.minimum_order_qty,
|
|
'req_qty': req_qty,
|
|
'part_no_id': component.id
|
|
}
|
|
|
|
# Process the aggregated quantities to update or create material plan lines
|
|
for part_no, data in part_no_quantities.items():
|
|
inhand_stock_qty = data['inhand_stock_qty']
|
|
minimum_stock_qty = data['minimum_stock_qty']
|
|
req_qty = data['req_qty']
|
|
minimum_order_qty = data['minimum_order_qty']
|
|
in_transit_stock_qty = data['in_transit_stock_qty']
|
|
|
|
# Check if the material line already exists
|
|
pick_line = material_line_model.search([
|
|
('plan_id', '=', self.id),
|
|
('material_name', '=', data['part_no_id'])
|
|
], limit=1)
|
|
|
|
if pick_line:
|
|
# Update existing material line
|
|
pick_line.write({
|
|
'actual_required_qty': pick_line.actual_required_qty + req_qty,
|
|
'required_qty': pick_line.required_qty + req_qty,
|
|
'approved_cnt': pick_line.approved_cnt + (req_qty if pick_line.approved_cnt else 0)
|
|
})
|
|
else:
|
|
# Calculate the approved count if creating a new line
|
|
if minimum_stock_qty > (inhand_stock_qty + in_transit_stock_qty) - req_qty:
|
|
approved_cnt = max(req_qty + minimum_stock_qty - (inhand_stock_qty + in_transit_stock_qty), minimum_order_qty)
|
|
else:
|
|
approved_cnt = 0
|
|
|
|
# Create a new material plan line
|
|
material_line_model.create({
|
|
'material_name': data['part_no_id'],
|
|
'inhand_qty': inhand_stock_qty,
|
|
'minimum_stock_qty': minimum_stock_qty,
|
|
'plan_id': self.id,
|
|
'required_qty': req_qty,
|
|
'actual_required_qty': req_qty, # Set correctly for new records
|
|
'approved_cnt': approved_cnt,
|
|
'minimum_order_qty': minimum_order_qty
|
|
})
|
|
|
|
|
|
def create_sfg_plan(self, line_ids):
|
|
sfg_stores_model = self.env['sos_sfg']
|
|
sfg_line_model = self.env['sos_sfg_plan_line']
|
|
|
|
# Loop through the lines to process each item
|
|
for item in line_ids:
|
|
if item.approved_cnt != 0:
|
|
fg_name = item.fg_name.id # Use the ID for Many2one relationship
|
|
fg_qty_to_produce = int(item.approved_cnt)
|
|
|
|
# Fetch the primary BOM for the FG
|
|
product_bom = self.env['sos_fg_bom'].search([
|
|
('fg_name', '=', fg_name),
|
|
('is_primary', '=', True)
|
|
], limit=1)
|
|
|
|
if product_bom:
|
|
lines = self.env['sos_fg_bom_line'].search([('bom_id', '=', product_bom.id)])
|
|
|
|
# Fetch existing SFG plan lines for the current plan
|
|
old_lines = sfg_line_model.search([('plan_id', '=', self.id)])
|
|
already_created_sfg = {line.sfg_name.id: line for line in old_lines}
|
|
|
|
for sfg_line in lines:
|
|
sfg_name = sfg_line.sfg_bom_id.name.id # Use the ID for Many2one relationship
|
|
req_sfg_qty = sfg_line.quantity * fg_qty_to_produce
|
|
|
|
sfg_inhand_qty = max(0, sfg_line.sfg_bom_id.name.inhand_stock_qty - sfg_line.sfg_bom_id.name.blocked_qty)
|
|
sfg_minimum_stock_qty = sfg_line.sfg_bom_id.name.minimum_stock_qty
|
|
sfg_intransit_stock_qty = sfg_line.sfg_bom_id.name.in_transit_stock_qty
|
|
|
|
# Calculate approved quantity
|
|
if sfg_minimum_stock_qty <= (sfg_inhand_qty + sfg_intransit_stock_qty) - req_sfg_qty:
|
|
approved_cnt = 0
|
|
else:
|
|
#approved_cnt = req_sfg_qty + sfg_minimum_stock_qty - (sfg_inhand_qty + sfg_intransit_stock_qty)
|
|
|
|
approved_cnt = req_sfg_qty - (sfg_inhand_qty + sfg_intransit_stock_qty)
|
|
|
|
# If the SFG already exists in the plan, update its values
|
|
if sfg_name in already_created_sfg:
|
|
existing_line = already_created_sfg[sfg_name]
|
|
|
|
# Determine the new approved count
|
|
if existing_line.actual_required_qty + req_sfg_qty > sfg_line.sfg_bom_id.name.minimum_order_qty:
|
|
order_to_be = existing_line.actual_required_qty + req_sfg_qty
|
|
else:
|
|
order_to_be = sfg_line.sfg_bom_id.name.minimum_order_qty
|
|
|
|
# Update the existing line
|
|
existing_line.write({
|
|
'required_qty': existing_line.required_qty + req_sfg_qty,
|
|
'actual_required_qty': existing_line.actual_required_qty + req_sfg_qty,
|
|
'approved_cnt': order_to_be,
|
|
'blowup_action': 'yes'
|
|
})
|
|
else:
|
|
# Create a new SFG plan line
|
|
sfg_line_model.create({
|
|
'fg_name': fg_name,
|
|
'sfg_name': sfg_name,
|
|
'inhand_qty': sfg_inhand_qty,
|
|
'minimum_stock_qty': sfg_minimum_stock_qty,
|
|
'plan_id': self.id,
|
|
'actual_required_qty': approved_cnt,
|
|
'required_qty': req_sfg_qty,
|
|
'approved_cnt': approved_cnt,
|
|
'blowup_action': 'yes',
|
|
'assembling_cost': sfg_line.sfg_bom_id.name.assembling_charges
|
|
})
|
|
|
|
|
|
material_lines = self.env['sos_sfg_bom_line'].search([('fg_bom_id', '=', product_bom.id)])
|
|
material_stores_model = self.env['sos_material']
|
|
material_line_model = self.env['sos_material_plan_line']
|
|
|
|
# Fetch existing material plan lines for the current plan
|
|
old_lines_material = material_line_model.search([('plan_id', '=', self.id)])
|
|
already_created_material = {line_material.material_name.id: line_material for line_material in old_lines_material}
|
|
|
|
for material_line in material_lines:
|
|
inhand_stock_qty = material_line.primary_component_id.inhand_stock_qty
|
|
minimum_stock_qty = material_line.primary_component_id.minimum_stock_qty
|
|
req_qty = material_line.quantity * fg_qty_to_produce
|
|
minimum_order_qty = material_line.primary_component_id.minimum_order_qty
|
|
intransit_qty_material = material_line.primary_component_id.in_transit_stock_qty
|
|
|
|
# Calculate the approved count for the material
|
|
if minimum_stock_qty > (inhand_stock_qty + intransit_qty_material) - req_qty:
|
|
#approved_cnt_material = max(req_qty + minimum_stock_qty - (inhand_stock_qty + intransit_qty_material), minimum_order_qty)
|
|
approved_cnt_material = max(req_qty - (inhand_stock_qty + intransit_qty_material), 0, minimum_order_qty)
|
|
|
|
else:
|
|
approved_cnt_material = 0
|
|
|
|
# Check if the material already exists in the current plan
|
|
if material_line.primary_component_id.id in already_created_material:
|
|
existing_line = already_created_material[material_line.primary_component_id.id]
|
|
# Accumulate approved_cnt
|
|
new_approved_cnt = existing_line.approved_cnt + approved_cnt_material
|
|
existing_line.write({
|
|
'actual_required_qty': existing_line.actual_required_qty + req_qty,
|
|
'required_qty': existing_line.required_qty + req_qty,
|
|
'approved_cnt': new_approved_cnt
|
|
})
|
|
else:
|
|
# Create a new material line
|
|
material_line_model.create({
|
|
'material_name': material_line.primary_component_id.id,
|
|
'inhand_qty': inhand_stock_qty,
|
|
'minimum_stock_qty': minimum_stock_qty,
|
|
'plan_id': self.id,
|
|
'minimum_order_qty': minimum_order_qty,
|
|
'actual_required_qty': req_qty,
|
|
'required_qty': req_qty,
|
|
'approved_cnt': approved_cnt_material
|
|
})
|
|
|
|
|
|
def create_production_plan(self,line_ids):
|
|
indent_model = self.env['sos_production_plan']
|
|
indent_record = indent_model.search([('plan_ref_no', '=', self.plan_ref_no)], limit=1)
|
|
|
|
if not indent_record:
|
|
for item in line_ids:
|
|
fg_name = item.fg_name.id
|
|
qp_no = item.fg_name.qp_no
|
|
fg_qty_to_produce = int(item.approved_cnt)
|
|
indent_model.create({
|
|
'plan_ref_no': self.plan_ref_no,
|
|
'fg_name': fg_name,
|
|
'qp_no': qp_no,
|
|
'required_qty': fg_qty_to_produce,
|
|
'target_date': self.target_date,
|
|
'indent_start_date' : self.indent_start_date
|
|
})
|
|
def create_fptc(self,line_ids):
|
|
tc_model = self.env['sos_transfer_challan']
|
|
tc_record = tc_model.search([('plan_ref_no', '=', self.plan_ref_no)], limit=1)
|
|
|
|
if not tc_record:
|
|
for item in line_ids:
|
|
fg_name = item.fg_name.id
|
|
tc_record_new = tc_model.create({
|
|
'plan_ref_no': self.plan_ref_no,
|
|
'planned_qty': item.approved_cnt,
|
|
'fg_name': fg_name,
|
|
'indent_start_date':self.indent_start_date,
|
|
'indent_target_date':self.target_date
|
|
|
|
})
|
|
if tc_record_new:
|
|
try:
|
|
specifications = self.env['sos_testing_parameters'].search([('fg_name', '=', fg_name)], limit=1)
|
|
|
|
if not specifications:
|
|
continue
|
|
|
|
lines = [(5, 0, 0)]
|
|
for param in specifications.specification_ids:
|
|
lines.append((0, 0, {
|
|
'specification': param.name,
|
|
'specification_value': ''
|
|
}))
|
|
tc_record_new.specification_line_ids = lines
|
|
|
|
except Exception as e:
|
|
_logger.error("Error processing specifications for FG %s: %s", fg_name, str(e))
|
|
continue
|
|
|
|
class SOS_FG_Plan_Line(models.Model):
|
|
_name = 'sos_fg_plan_line'
|
|
_description = 'FG Plan Lines'
|
|
_order = 'approved_cnt desc'
|
|
|
|
fg_name = fields.Many2one('sos_fg',string="FG Name")
|
|
inhand_qty = fields.Integer(string="FG Stocks")
|
|
minimum_stock_qty = fields.Integer(string="Minimum Stock Qty")
|
|
in_transit_stock_qty = fields.Integer(string="In-transit Stock Qty")
|
|
plan_id = fields.Many2one('sos_fg_plan', string='FG Plan', ondelete='cascade')
|
|
required_qty = fields.Integer(string='Required Qty')
|
|
actual_required_qty = fields.Integer(string='Actual Req Qty')
|
|
minimum_order_qty = fields.Integer(string='Minimum Order Qty')
|
|
approved_cnt = fields.Integer(string='Planned Qty', store=True)
|
|
planned_week_1 = fields.Integer(string='Week 1')
|
|
planned_week_2 = fields.Integer(string='Week 2')
|
|
planned_week_3 = fields.Integer(string='Week 3')
|
|
planned_week_4 = fields.Integer(string='Week 4')
|
|
planned_week_5 = fields.Integer(string='Week 5')
|
|
planned_week_6 = fields.Integer(string='Week 6')
|
|
planned_week_7 = fields.Integer(string='Week 7')
|
|
planned_week_8 = fields.Integer(string='Week 8')
|
|
completed_qty = fields.Integer(string='Completed Qty', compute='_compute_completed', store=True)
|
|
|
|
@api.depends('planned_week_1', 'planned_week_2', 'planned_week_3', 'planned_week_4', 'planned_week_5', 'planned_week_6', 'planned_week_7', 'planned_week_8')
|
|
def _compute_completed(self):
|
|
for record in self:
|
|
total_completed = (
|
|
record.planned_week_1 +
|
|
record.planned_week_2 +
|
|
record.planned_week_3 +
|
|
record.planned_week_4 +
|
|
record.planned_week_5 +
|
|
record.planned_week_6 +
|
|
record.planned_week_7 +
|
|
record.planned_week_8
|
|
)
|
|
record.completed_qty = total_completed
|
|
|
|
|
|
|
|
class SOS_SFG_Plan_Line(models.Model):
|
|
_name = 'sos_sfg_plan_line'
|
|
_description = 'SFG Plan Lines'
|
|
_order = 'approved_cnt desc'
|
|
fg_name = fields.Many2one('sos_fg',string="FG Name")
|
|
company_id = fields.Many2one('res.company', store=True, copy=False,
|
|
string="Company",
|
|
default=lambda self: self.env.user.company_id.id)
|
|
currency_id = fields.Many2one('res.currency', string="Currency",
|
|
related='company_id.currency_id',
|
|
default=lambda
|
|
self: self.env.user.company_id.currency_id.id)
|
|
sfg_name = fields.Many2one('sos_sfg',string="SFG Name")
|
|
assembling_cost = fields.Monetary(currency_field='currency_id',string="Assembling Cost per unit")
|
|
blowup_action = fields.Selection(
|
|
[('yes', 'Yes'), ('no', 'No')],
|
|
string='Blowup',default="yes"
|
|
)
|
|
inhand_qty = fields.Integer(string="SFG Stocks")
|
|
minimum_stock_qty = fields.Integer(string="Minimum Stock Qty")
|
|
in_transit_stock_qty = fields.Integer(string="In-transit Stock Qty")
|
|
minimum_order_qty = fields.Integer(string="Minimum Order Qty")
|
|
plan_id = fields.Many2one('sos_fg_plan', string='SFG Plan', ondelete='cascade')
|
|
required_qty = fields.Integer(string='Required Qty')
|
|
approved_cnt = fields.Integer(string='Planned Qty',default='1')
|
|
actual_required_qty = fields.Integer(string='Actual Req Qty')
|
|
total_assembling_cost = fields.Float(compute='_compute_total_assembling_cost', string='Total Assembling Cost')
|
|
@api.onchange('sfg_name')
|
|
def _onchange_sfg_name(self):
|
|
for record in self:
|
|
if record.sfg_name:
|
|
record.assembling_cost = record.sfg_name.assembling_charges
|
|
@api.depends('assembling_cost', 'approved_cnt')
|
|
def _compute_total_assembling_cost(self):
|
|
for record in self:
|
|
if record.assembling_cost is not None and record.approved_cnt is not None:
|
|
record.total_assembling_cost = record.assembling_cost * record.approved_cnt
|
|
else:
|
|
record.total_assembling_cost = 0
|
|
|
|
|
|
class SOS_Material_Plan_Line(models.Model):
|
|
_name = 'sos_material_plan_line'
|
|
_description = 'Material Plan Lines'
|
|
_order = 'approved_cnt desc'
|
|
|
|
material_name = fields.Many2one('sos_material',string="Material Name")
|
|
company_id = fields.Many2one('res.company', store=True, copy=False,
|
|
string="Company",
|
|
default=lambda self: self.env.user.company_id.id)
|
|
currency_id = fields.Many2one('res.currency', string="Currency",
|
|
related='company_id.currency_id',
|
|
default=lambda
|
|
self: self.env.user.company_id.currency_id.id)
|
|
inhand_qty = fields.Integer(string="Material Stocks")
|
|
in_transit_stock_qty = fields.Integer(string="In-transit Stock Qty")
|
|
minimum_stock_qty = fields.Integer(string="Minimum Stock Qty")
|
|
plan_id = fields.Many2one('sos_fg_plan', string='Material Plan', ondelete='cascade')
|
|
required_qty = fields.Integer(string='Required Qty')
|
|
actual_required_qty = fields.Integer(string='Actual Req Qty')
|
|
minimum_order_qty = fields.Integer(string='Minimum Order Qty')
|
|
approved_cnt = fields.Integer(string='Planned Qty')
|
|
approx_price = fields.Monetary(related="material_name.unit_price",currency_field='currency_id', string='Unit Price',store=True)
|
|
total_approx_price = fields.Float(compute='_compute_total_approx_price', string='Approx Price',store=True)
|
|
@api.onchange('material_name')
|
|
def _onchange_material_name(self):
|
|
for record in self:
|
|
if record.material_name:
|
|
record.approx_price = record.material_name.unit_price
|
|
@api.depends('approx_price', 'approved_cnt')
|
|
def _compute_total_approx_price(self):
|
|
for record in self:
|
|
if record.approx_price is not None and record.approved_cnt is not None:
|
|
record.total_approx_price = record.approx_price * record.approved_cnt
|
|
else:
|
|
record.total_approx_price = 0
|
|
|
|
|
|
|
|
|