from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from datetime import datetime, timedelta import logging from pytz import timezone, utc import io import base64 try: from odoo.tools.misc import xlsxwriter except ImportError: import xlsxwriter _logger = logging.getLogger(__name__) class PurchaseOrder(models.Model): _inherit = 'purchase.order' order_sales_match_line = fields.One2many('purchase.order.sales.match', 'purchase_order_id', string='Sales Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) sale_order_id = fields.Many2one('sale.order', string='Sale Order') procurement_status = fields.Char(string='Procurement Status', compute='get_procurement_status', readonly=True) po_status = fields.Selection([ ('terproses', 'Terproses'), ('sebagian', 'Sebagian Diproses'), ('menunggu', 'Menunggu Diproses'), ]) approval_status = fields.Selection([ ('pengajuan1', 'Approval Manager'), #siapa? darren - 11 ('pengajuan2', 'Approval Pimpinan'), #akbar - 7 temporary not used ('approved', 'Approved'), ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3) delivery_amount = fields.Float('Delivery Amount', compute='compute_delivery_amount') total_margin = fields.Float( 'Margin', compute='compute_total_margin', help="Total Margin in Sales Order Header") total_percent_margin = fields.Float( 'Margin%', compute='compute_total_margin', help="Total % Margin in Sales Order Header") total_so_margin = fields.Float( 'SO Margin', compute='compute_total_margin', help="Total Margin in Sales Order Header") total_so_percent_margin = fields.Float( 'SO Margin%', compute='compute_total_margin', help="Total % Margin in Sales Order Header") amount_total_without_service = fields.Float('AmtTotalWithoutService', compute='compute_amt_total_without_service') summary_qty_po = fields.Float('Total Qty', compute='_compute_summary_qty') summary_qty_receipt = fields.Float('Summary Qty Receipt', compute='_compute_summary_qty') count_line_product = fields.Float('Total Item', compute='compute_count_line_product') note_description = fields.Char(string='Noteman', help='bisa diisi sebagai informasi indent barang tertentu atau apapun') has_active_invoice = fields.Boolean(string='Has Active Invoice', compute='_compute_has_active_invoice') description = fields.Char(string='Description', help='bisa diisi sebagai informasi indent barang tertentu atau apapun') purchase_order_lines = fields.One2many('purchase.order.line', 'order_id', string='Indent', auto_join=True) responsible_ids = fields.Many2many('res.users', string='Responsibles', compute='_compute_responsibles') status_paid_cbd = fields.Boolean(string='Paid Status', tracking=3, help='Field ini diisi secara manual oleh Finance AP dan hanya untuk status PO CBD') revisi_po = fields.Boolean(string='Revisi', tracking=3) from_apo = fields.Boolean(string='From APO', tracking=3) approval_edit_line = fields.Boolean(string='Approval Edit Line', tracking=3) @api.model def action_multi_cancel(self): for purchase in self: purchase.update({ 'state': 'cancel', }) if purchase.state == 'cancel': purchase.update({ 'approval_status': False, }) def open_form_multi_cancel(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_po_multi_cancel') action['context'] = { 'purchase_ids': [x.id for x in self] } return action def delete_line(self): lines_to_delete = self.order_line.filtered(lambda line: line.suggest == 'masih cukup') if not lines_to_delete: raise UserError('Tidak ada item yang masih cukup') lines_to_delete.unlink() def open_form_multi_confirm_po(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_confirm') action['context'] = { 'order_ids': [x.id for x in self] } return action def action_multi_update_paid_status(self): for purchase in self: purchase.update({ 'status_paid_cbd': True, }) def action_multi_confirm_po(self): for purchase in self: if purchase.state != 'draft': continue purchase.button_confirm() def open_form_multi_update_paid_status(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_update') action['context'] = { 'purchase_ids': [x.id for x in self] } return action def _compute_responsibles(self): for purchase in self: resposible_ids = [] for line in purchase.order_line: resposible_ids.append(line.product_id.x_manufacture.user_id.id) resposible_ids = list(set(resposible_ids)) purchase.responsible_ids = resposible_ids def _compute_has_active_invoice(self): for order in self: order.has_active_invoice = any(invoice.state != 'cancel' for invoice in order.invoice_ids) def add_product_to_pricelist(self): i = 0 for line in self.order_line: i += 1 current_time = datetime.utcnow() # print(i, len(self.order_line)) price_unit = line.price_unit taxes = line.taxes_id # for tax in taxes: # tax_include = tax.price_include # if taxes: # if tax_include: # price_unit = price_unit # else: # price_unit = price_unit + (price_unit * 11 / 100) # else: # price_unit = price_unit + (price_unit * 11 / 100) purchase_pricelist = self.env['purchase.pricelist'].search([ ('product_id', '=', line.product_id.id), ('vendor_id', '=', line.order_id.partner_id.id) ]) purchase_pricelist = purchase_pricelist.with_context(update_by='system') if not purchase_pricelist: purchase_pricelist.create([{ 'vendor_id': line.order_id.partner_id.id, 'product_id': line.product_id.id, 'product_price': 0, 'taxes_system_id': taxes.id, 'system_price': price_unit, 'system_last_update': current_time }]) else: purchase_pricelist.write({ 'system_last_update': current_time, 'taxes_system_id': taxes.id, 'system_price': price_unit }) def _compute_date_planned(self): for order in self: if order.date_approve: leadtime = order.partner_id.leadtime current_time = order.date_approve delta_time = current_time + timedelta(days=leadtime) delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') order.date_planned = delta_time else: order.date_planned = False def action_create_invoice(self): res = super(PurchaseOrder, self).action_create_invoice() if not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa membuat Bill') return res def calculate_line_no(self): line_no = 0 for line in self.order_line: if line.product_id.type == 'product': line_no += 1 line.line_no = line_no # _logger.info('Calculate PO Line No %s' % line.id) def calculate_po_status_beginning(self): purchases = self.env['purchase.order'].search([ ('po_status', '=', False), '|', ('state', '=', 'purchase'), ('state', '=', 'done') ]) for order in purchases: sum_qty_received = sum_qty_po = 0 for po_line in order.order_line: sum_qty_po += po_line.product_uom_qty sum_qty_received += po_line.qty_received if order.summary_qty_po == order.summary_qty_receipt: order.po_status = 'terproses' elif order.summary_qty_po > order.summary_qty_receipt > 0: order.po_status = 'sebagian' else: order.po_status = 'menunggu' _logger.info("Calculate PO Status %s" % order.id) def calculate_po_status(self): purchases = self.env['purchase.order'].search([ ('po_status', '!=', 'terproses'), # ('id', '=', 213), ]) for order in purchases: sum_qty_received = sum_qty_po = 0 have_outstanding_pick = False for pick in order.picking_ids: if pick.state == 'draft' or pick.state == 'assigned' or pick.state == 'confirmed' or pick.state == 'waiting': have_outstanding_pick = True for po_line in order.order_line: sum_qty_po += po_line.product_uom_qty sum_qty_received += po_line.qty_received if have_outstanding_pick: # if order.summary_qty_po == order.summary_qty_receipt: # order.po_status = 'terproses' if order.summary_qty_po > order.summary_qty_receipt > 0: order.po_status = 'sebagian' else: order.po_status = 'menunggu' else: order.po_status = 'terproses' _logger.info("Calculate PO Status %s" % order.id) def _compute_summary_qty(self): for order in self: sum_qty_po = sum_qty_receipt = 0 for line in order.order_line: sum_qty_po += line.product_uom_qty sum_qty_receipt += line.qty_received order.summary_qty_po = sum_qty_po order.summary_qty_receipt = sum_qty_receipt def get_procurement_status(self): for purchase_order in self: # product_uom_qty = sum_qty_received = 0 # # for order_line in purchase_order.order_line: # product_uom_qty += order_line.product_uom_qty # sum_qty_received += order_line.qty_received if purchase_order.summary_qty_po == purchase_order.summary_qty_receipt: status = 'Terproses' elif purchase_order.summary_qty_po > purchase_order.summary_qty_receipt > 0: status = 'Sebagian Diproses' else: status = 'Menunggu Diproses' purchase_order.procurement_status = status def sale_order_sync(self): if not self.sale_order_id: return purchase_orders = self.search(['&', ('sale_order_id', '=', self.sale_order_id.id), ('id', '!=', self.id)]) products_exception = [] for purchase_order in purchase_orders: for order_line in purchase_order.order_line: products_exception.append(order_line.product_id.id) self.order_line.unlink() for order_line in self.sale_order_id.order_line: if order_line.vendor_id != self.partner_id: continue if order_line.product_id.id and order_line.product_id.id not in products_exception: qty_available = order_line.product_id.qty_onhand_bandengan + order_line.product_id.qty_incoming_bandengan - order_line.product_id.outgoing_qty # suggest = 'harus beli' # if qty_available > order_line.product_qty: # suggest = 'masih cukup' values = { 'order_id': self.id, 'product_id': order_line.product_id.id, 'name': order_line.product_id.display_name, 'product_qty': order_line.product_qty, 'qty_available_store': qty_available, # 'suggest': suggest, 'so_line_id': order_line.id, } self.order_line.create(values) for order_line in self.order_line: order_line.suggest_purchasing() def compute_count_line_product(self): for order in self: count = 0 for line in order.order_line: if line.product_id.type == 'product': count += 1 if count == 0: order.count_line_product = 1 else: order.count_line_product = count def compute_delivery_amount(self): for order in self: amount = 0 for line in order.order_line: if line.product_id.type == 'service': amount += line.price_total order.delivery_amount = amount def date_deadline_ref_date_planned(self): for picking in self.picking_ids: if picking.state in ['done', 'cancel']: continue picking.scheduled_date = self.date_planned picking.date_deadline = self.date_planned def button_confirm(self): res = super(PurchaseOrder, self).button_confirm() current_time = datetime.now() if self.total_percent_margin < self.total_so_percent_margin and not self.env.user.is_purchasing_manager and not self.env.user.is_leader: raise UserError("Beda Margin dengan Sales, harus approval Manager") if not self.from_apo: if not self.sale_order_id and not self.env.user.is_purchasing_manager and not self.env.user.is_leader: raise UserError("Tidak ada link dengan SO, harus approval Manager") send_email = False self.add_product_to_pricelist() for line in self.order_line: if not line.product_id.purchase_ok: raise UserError("Terdapat barang yang tidak bisa diproses") if line.price_unit != line.price_vendor and line.price_vendor != 0: self._send_po_not_sync() send_email = True break if send_email: self._send_mail() if self.revisi_po: delta_time = current_time - timedelta(days=2) delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') self.date_approve = delta_time self.approval_status = 'approved' self.po_status = 'menunggu' self.calculate_line_no() # override date planned added with two days leadtime = self.partner_id.leadtime delta_time = current_time + timedelta(days=leadtime) delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') self.date_planned = delta_time self.date_deadline_ref_date_planned() return res def _send_po_not_sync(self): # Mengirim data ke model Po Sync Price jika harga po dan purchase pricelist tidak singkron for line in self.order_line: if line.price_unit != line.price_vendor and line.price_vendor != 0: self.env['po.sync.price'].create([{ 'order_line_id': line.id, }]) def _send_mail(self): output = io.BytesIO() workbook = xlsxwriter.Workbook(output, {'in_memory': True}) worksheet = workbook.add_worksheet() format6 = workbook.add_format({'font_size': 12, 'align': 'center', 'bg_color': '#D3D3D3', 'bold': True}) format1 = workbook.add_format({'font_size': 11, 'align': 'center', 'valign': 'vcenter'}) worksheet.set_column(0, 0, 10) worksheet.set_column(1, 1, 20) worksheet.set_column(2, 2, 20) worksheet.set_column(3, 3, 20) worksheet.set_column(4, 4, 15) worksheet.set_column(5, 5, 15) worksheet.write('A1', 'PO', format6) worksheet.write('B1', 'SKU', format6) worksheet.write('C1', 'Product', format6) worksheet.write('D1', 'Brand', format6) worksheet.write('E1', 'PO Price', format6) worksheet.write('F1', 'Purchase Pricelist', format6) worksheet.write('G1', 'Created On', format6) row_number = 1 po_sync = self.env['po.sync.price'].search([], order='create_date desc') for po in po_sync: worksheet.write(row_number, 0, po.order_line_id.order_id.name, format1) worksheet.write(row_number, 1, po.order_line_id.product_id.default_code, format1) worksheet.write(row_number, 2, po.order_line_id.product_id.name, format1) worksheet.write(row_number, 3, po.order_line_id.product_id.x_manufacture.x_name, format1) worksheet.write(row_number, 4, po.order_line_id.price_unit, format1) worksheet.write(row_number, 5, po.order_line_id.price_vendor, format1) worksheet.write(row_number, 6, po.create_date.replace(tzinfo=utc).astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S'), format1) row_number += 1 workbook.close() output.seek(0) template = self.env.ref('indoteknik_custom.mail_template_po_sync_price') template.attachment_ids.unlink() attachment_vals = { 'name': 'Purchase Order.xlsx', 'datas': base64.b64encode(output.read()), 'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'res_model': 'mail.template', 'res_id': template.id, } attachment_id = self.env['ir.attachment'].create(attachment_vals) template.attachment_ids = [(4, attachment_id.id)] template.send_mail(self.id, force_send=True) def po_approve(self): if self.env.user.is_leader or self.env.user.is_purchasing_manager: raise UserError("Bisa langsung Confirm") elif self.total_percent_margin == self.total_so_percent_margin and self.sale_order_id: raise UserError("Bisa langsung Confirm") else: self.approval_status = 'pengajuan1' def re_calculate(self): for line in self.order_line: sale_order_line = self.env['sale.order.line'].search([ ('product_id', 'in', [line.product_id.id]), ('order_id', '=', line.order_id.sale_order_id.id) ]) for so_line in sale_order_line: so_line.purchase_price = line.price_unit def button_cancel(self): res = super(PurchaseOrder, self).button_cancel() self.approval_status = False return res def compute_total_margin(self): for rec in self: if rec.from_apo: rec.compute_total_margin_from_apo() return sum_so_margin = sum_sales_price = sum_margin = 0 for line in self.order_line: sale_order_line = line.so_line_id if not sale_order_line: sale_order_line = self.env['sale.order.line'].search([ ('product_id', '=', line.product_id.id), ('order_id', '=', line.order_id.sale_order_id.id) ], limit=1, order='price_reduce_taxexcl') sum_so_margin += sale_order_line.item_margin sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': sales_price -= sale_order_line.delivery_amt_line if sale_order_line.order_id.fee_third_party > 0: sales_price -= sale_order_line.fee_third_party_line sum_sales_price += sales_price purchase_price = line.price_subtotal if line.order_id.delivery_amount > 0: purchase_price += line.delivery_amt_line real_item_margin = sales_price - purchase_price sum_margin += real_item_margin if sum_so_margin != 0 and sum_sales_price != 0 and sum_margin != 0: self.total_so_margin = sum_so_margin self.total_so_percent_margin = round((sum_so_margin / sum_sales_price), 2) * 100 self.total_margin = sum_margin self.total_percent_margin = round((sum_margin / sum_sales_price), 2) * 100 else: self.total_margin = 0 self.total_percent_margin = 0 self.total_so_margin = 0 self.total_so_percent_margin = 0 def compute_total_margin_from_apo(self): sum_so_margin = sum_sales_price = sum_margin = 0 for line in self.order_sales_match_line: po_line = self.env['purchase.order.line'].search([ ('product_id', '=', line.product_id.id), ('order_id', '=', line.purchase_order_id.id) ], limit=1) sale_order_line = line.sale_line_id if not sale_order_line: sale_order_line = self.env['sale.order.line'].search([ ('product_id', '=', line.product_id.id), ('order_id', '=', line.sale_id.id) ], limit=1, order='price_reduce_taxexcl') sum_so_margin += line.qty_po / line.qty_so * sale_order_line.item_margin # sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty sales_price = sale_order_line.price_reduce_taxexcl * po_line.product_qty if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': sales_price -= sale_order_line.delivery_amt_line if sale_order_line.order_id.fee_third_party > 0: sales_price -= sale_order_line.fee_third_party_line sum_sales_price += sales_price purchase_price = po_line.price_subtotal if line.purchase_order_id.delivery_amount > 0: purchase_price += po_line.delivery_amt_line real_item_margin = sales_price - purchase_price sum_margin += real_item_margin if sum_so_margin != 0 and sum_sales_price != 0 and sum_margin != 0: self.total_so_margin = sum_so_margin self.total_so_percent_margin = round((sum_so_margin / sum_sales_price), 2) * 100 self.total_margin = sum_margin self.total_percent_margin = round((sum_margin / sum_sales_price), 2) * 100 else: self.total_margin = 0 self.total_percent_margin = 0 self.total_so_margin = 0 self.total_so_percent_margin = 0 # def compute_total_margin_from_apo(self): # purchase_price_dict = {} # for line in self.order_sales_match_line: # for lines in self.order_line: # product_id = lines.product_id.id # if product_id not in purchase_price_dict: # purchase_price_dict[product_id] = lines.price_subtotal # sum_so_margin = sum_sales_price = sum_margin = 0 # sale_order_line = line.sale_line_id # if not sale_order_line: # sale_order_line = self.env['sale.order.line'].search([ # ('product_id', '=', line.product_id.id), # ('order_id', '=', line.sale_id.id) # ], limit=1, order='price_reduce_taxexcl') # sum_so_margin += sale_order_line.item_margin # # sales_price = sale_order_line.price_reduce_taxexcl * line.qty_so # sales_price = sale_order_line.price_reduce_taxexcl * lines.product_qty # if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': # sales_price -= sale_order_line.delivery_amt_line # if sale_order_line.order_id.fee_third_party > 0: # sales_price -= sale_order_line.fee_third_party_line # sum_sales_price += sales_price # product_id = sale_order_line.product_id.id # purchase_price = purchase_price_dict.get(product_id, 0) # # purchase_price = lines.price_subtotal # if lines.order_id.delivery_amount > 0: # purchase_price += lines.delivery_amt_line # if line.purchase_order_id.delivery_amount > 0: # purchase_price += line.delivery_amt_line # real_item_margin = sales_price - purchase_price # sum_margin += real_item_margin # if sum_so_margin != 0 and sum_sales_price != 0 and sum_margin != 0: # self.total_so_margin = sum_so_margin # self.total_so_percent_margin = round((sum_so_margin / sum_sales_price), 2) * 100 # self.total_margin = sum_margin # self.total_percent_margin = round((sum_margin / sum_sales_price), 2) * 100 # else: # self.total_margin = 0 # self.total_percent_margin = 0 # self.total_so_margin = 0 # self.total_so_percent_margin = 0 def compute_amt_total_without_service(self): for order in self: sum_price_total = 0 for line in order.order_line: if line.product_id.type == 'product': sum_price_total += line.price_total order.amount_total_without_service = sum_price_total