# -*- coding: utf-8 -*- from odoo import models, fields, api import re import math class sos__po(models.Model): _name = 'sos_po' _description = 'Purchase Order' _rec_name = 'po_no' _order = 'id desc' def _default_invoicing_address(self): html_content = self.env.company.company_details if not isinstance(html_content, str): html_content = str(html_content) clean_text = re.sub(r'

', '\n', html_content) clean_text = re.sub(r'<[^>]+>', '', clean_text) lines = clean_text.split('\n') for line in lines: if line.strip(): line.strip() return clean_text plan_ref_no = fields.Char( string='Indent No' ) supplier_name = fields.Many2one('sos_suppliers',string="Supplier Name", required= True) supplier_address = fields.Text(related='supplier_name.address',string="Supplier Address") po_no = fields.Char(string="P.O No", readonly= True, required= True, default=lambda self: self._generate_id()) po_date = fields.Datetime(string="P.O Date",required=True) quotation_no = fields.Char(string="Quotation No") quotation_date = fields.Date(string="Quotation Date") material_delivery_required_date = fields.Date(string="Material Delivery Required Date") supplier_gst_no = fields.Char(related='supplier_name.gst_no', string="Supplier GST No", readonly=False) sosaley_gst_no = fields.Char(string="Sosaley GST No",default="33AACCJ0045R1ZI") invoicing_address = fields.Text(string="Invoicing Address",default= _default_invoicing_address) delivery_address = fields.Text(string="Delivery Address",default= _default_invoicing_address) sarf_no = fields.Char(related='supplier_name.supplier_code',string="SARF No") Contact_details = fields.Char(string="Contact Person Name & Mobile No") terms_conditions = fields.Html( string="Terms and Conditions", default="""

Terms and Conditions:


1. This Purchase Order is valid up to …………………………….
2. Payment: ................. from the date of receipt of the Invoice.
3. Invoice and Material Final Inspection Report should be provided with all the Supplies made.
4. Sosaley P.O. No. should be mentioned in your Delivery challan / Invoice.
5. All the products shall be supplied with closed boxes/containers and labelled.
6. All the boxes/containers shall be numbered (1 of 3, 2 of 3, etc.) """ ) 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) line_ids = fields.One2many('sos_po_line', 'po_id', string="Components", ondelete='cascade') gross_value = fields.Monetary(compute='_compute_gross_value', string="Gross Value", currency_field='currency_id', readonly=True) delivery_charges = fields.Monetary(string="Delivery Charges", currency_field='currency_id') delivery_tax = fields.Integer(default=18,string="Tax (%)") tax = fields.Selection( [ ('6', '6%'), ('12', '12%'), ('18', '18%'), ('28', '28%') ], string="Tax (%)", default='18' ) total_value = fields.Monetary(compute='_compute_total_value', string="Total Value", currency_field='currency_id', readonly=True) approval_status=fields.Selection([('Approved', 'Approve'),('Rejected', 'Reject')],string="Approval Status") indent_id = fields.Integer(String="Integer") total_qty = fields.Integer(string='Total Line Items Count', compute='_compute_total_qty', store=True) received_qty = fields.Integer(string='Received Line Items Count', store=True) payment_method=fields.Selection([('cash', 'Cash'),('neft', 'NEFT'),('credit', 'Credit'),('credit_card', 'Credit Card')],string="Payment Method") po_status = fields.Selection([ ('amend', 'Amended'),('open', 'Open'),('close', 'Closed')], default='open' , string="Status") progress = fields.Float(string="Completion Percentage", compute='_compute_progress', store=True, group_operator=False) stores_approved_by = fields.Many2one('res.users', string='Stores Approval By') stores_approved_image = fields.Image(related="stores_approved_by.signature_image",string='Top Management Approval Sign',readonly=True) stores_approved_on = fields.Datetime(string="Approved On") stores_manager_approved_by = fields.Many2one('res.users', string='Manager Approval By') stores_manager_approved_image = fields.Image(related="stores_manager_approved_by.signature_image",string='Top Management Approval Sign',readonly=True) stores_manager_approved_on = fields.Datetime(string="Approved On") top_management_approved_by = fields.Many2one('res.users', string='Top Management Approver') top_management_approval_image = fields.Image(related="top_management_approved_by.signature_image",string='Top Management Approval',readonly=True) top_management_approved_on = fields.Datetime(string="Approved On") rounded_total_value = fields.Float(string="Total", compute="_compute_total_value", store=True) delivery_total_value = fields.Float(string="Total", store=True) adjustment_value = fields.Float(string="Round-off", compute="_compute_total_value", store=True) tax_amount = fields.Float(string="Tax Value", store=True,readonly=True) delivery_tax_amount = fields.Float(string="Tax Value", store=True,readonly=True, compute="_compute_deliverytax") remarks = fields.Text(string="Remarks") # @api.model # def create(self, vals): # record = super(sos__po, self).create(vals) # record.action_esign_btn() # return record def action_amend(self): active_ids = self.env.context.get('active_ids', []) records = self.browse(active_ids) for record in records: record.po_status = 'amend' return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': 'Amended', 'message': 'The selected record(s) have been amended.', 'sticky': False, } } def action_esign_btn(self): sequence_util = self.env['sos_common_scripts'] sequence_util.action_assign_signature( self, 'stores_approved_by', 'stores_approved_on', 'sos_inventory.sos_scg_group_user' ) # Email part body_html = f"""

Below PO is waiting for your Approval

""" send_email = self.env['sos_common_scripts'] send_email.send_group_email(self.env,"sos_po",self.id,"deenalaura.m@sosaley.in","PO Approval Request",body_html,'sos_inventory.sos_scg_group_manager') # Email part ends def action_head_esign_btn(self): # Email part body_html = f"""

Below PO is waiting for your Approval

""" subject = f"Purchase Order Approval Request - {self.po_no}" send_email = self.env['sos_common_scripts'] send_email.send_direct_email(self.env,"sos_po",self.id,"ramachandran.r@sosaley.in",subject,body_html) # Email part ends sequence_util = self.env['sos_common_scripts'] return sequence_util.action_assign_signature( self, 'stores_manager_approved_by', 'stores_manager_approved_on' ) def action_top_esign_btn(self): sequence_util = self.env['sos_common_scripts'] sequence_util.action_assign_signature( self, 'top_management_approved_by', 'top_management_approved_on', 'sos_inventory.sos_management_user' ) body_html = f"""

Below PO is Approved

""" send_email = self.env['sos_common_scripts'] send_email.send_direct_email(self.env,"sos_po",self.id,"accounts@sosaley.in","PO Approved",body_html) if self.stores_approved_by and self.stores_approved_by.login: send_email.send_direct_email(self.env,"sos_po",self.id,self.stores_approved_by.login,"PO Approved",body_html) @api.depends('total_qty', 'received_qty', 'line_ids.prev_received_qty', 'line_ids.quantity') # ✅ Added 'line_ids.quantity' def _compute_progress(self): for record in self: total_quantity = sum(record.line_ids.mapped('quantity')) prev_received_quantity = sum(record.line_ids.mapped('prev_received_qty')) if total_quantity > 0: percentage = int((prev_received_quantity / total_quantity) * 100) else: percentage = 0 record.progress = percentage if percentage >= 100: record.progress = 100 record.po_status = "close" else: record.po_status = "open" # @api.depends('total_qty', 'received_qty') # def _compute_progress(self): # for po in self: # if po.total_qty > 0: # percentage = (po.received_qty / po.total_qty) * 100 # po.progress = percentage # if percentage >= 100: # po.progress = 100 # po.po_status = "close" # else: # po.progress = 0 @api.depends('line_ids') def _compute_total_qty(self): for po in self: po.total_qty = len(po.line_ids) def _generate_id(self): sequence_util = self.env['sos_common_scripts'] return sequence_util.generate_sequence('sos_po','PO', 'po_no') @api.depends('line_ids.total_price') def _compute_gross_value(self): for record in self: record.gross_value = round(sum(line.total_price for line in record.line_ids), 2) @api.depends('delivery_charges', 'delivery_tax') def _compute_deliverytax(self): for record in self: if record.delivery_charges: delivery_tax_amount = round((record.delivery_tax * record.delivery_charges) / 100, 2) exact_total = round(record.delivery_charges + delivery_tax_amount, 2) if math.isnan(exact_total) or exact_total is None: record.total_value = 0.00 record.delivery_total_value = 0.00 else: rounded_total = round(exact_total) record.delivery_total_value = exact_total record.delivery_tax_amount = delivery_tax_amount else: record.delivery_total_value = 0.00 record.delivery_tax_amount = 0.00 @api.depends('gross_value', 'tax','delivery_total_value') def _compute_total_value(self): for record in self: if record.gross_value: tax_amount = round((int(record.tax) * record.gross_value) / 100, 2) exact_total = round(record.delivery_total_value + record.gross_value + tax_amount, 2) if math.isnan(exact_total) or exact_total is None: record.total_value = 0.00 record.rounded_total_value = 0.00 record.adjustment_value = 0.00 else: rounded_total = round(exact_total) adjustment = round(rounded_total - exact_total, 2) record.total_value = exact_total record.rounded_total_value = rounded_total record.adjustment_value = adjustment record.tax_amount = tax_amount else: record.total_value = 0.00 record.rounded_total_value = 0.00 record.adjustment_value = 0.00 record.tax_amount = 0.00 def action_report_po_btn(self): try: action = self.env.ref("sos_inventory.action_report_po").report_action(self) return action except ValueError as e: print(f"Failed to find report action: {e}") def action_approve_po_btn(self): for record in self: record.approval_status = "Approved" return { 'type':'ir.actions.client', 'tag':'display_notification', 'params':{ 'message':'Approved' } } class Po_Line(models.Model): _name = 'sos_po_line' _description = 'PO Material Lines' po_id = fields.Many2one('sos_po', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_material', string="Material Name", required=True) material_code = fields.Char(related='component_id.material_code',string="Material Code") qp = fields.Char(related='component_id.qp_no',string="QP #") hsn_code = fields.Char(related='component_id.hsn_code',string="HSN Code #") unit_price = fields.Monetary(string="Unit Price") quantity = fields.Integer(string="Qty", required=True, default=1) prev_received_qty = fields.Integer() currency_id = fields.Many2one('res.currency', string='Currency') total_price = fields.Monetary(compute='_compute_total_cost', string="Total Price", currency_field='currency_id') status = fields.Integer(string="Status",default=0) @api.onchange('prev_received_qty') def _onchange_prev_received_qty(self): if self.prev_received_qty >= self.quantity: self.status = 2 @api.onchange('component_id') def _onchange_component_id(self): if self.component_id: self.unit_price = self.component_id.unit_price else: self.unit_price = 0.0 # Default value when no component is selected @api.depends('unit_price', 'quantity') def _compute_total_cost(self): for record in self: record.total_price = record.unit_price * record.quantity