from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT import logging from dateutil.relativedelta import relativedelta from datetime import datetime _logger = logging.getLogger(__name__) class PurchaseOrderLine(models.Model): _inherit = 'purchase.order.line' item_margin = fields.Float( 'Margin', compute='compute_item_margin', help="Total Margin in Sales Order Header") item_percent_margin = fields.Float( 'Margin%', compute='compute_item_margin', help="Total % Margin in Sales Order Header") so_item_margin = fields.Float( 'SO Margin', compute='compute_item_margin', help="Total Margin in Sales Order Header") so_item_percent_margin = fields.Float( 'SO Margin%', compute='compute_item_margin', help="Total % Margin in Sales Order Header") delivery_amt_line = fields.Float('DeliveryAmtLine', compute='compute_delivery_amt_line') line_no = fields.Integer('No', default=0) qty_available = fields.Float('Qty Available', compute='_compute_qty_stock') qty_onhand = fields.Float('Qty On Hand', compute='_compute_qty_stock') qty_incoming = fields.Float('Qty Incoming', compute='_compute_qty_stock') qty_outgoing = fields.Float('Qty Outgoing', compute='_compute_qty_stock') qty_available_store = fields.Float(string='Available') suggest = fields.Char(string='Suggest') price_vendor = fields.Float(string='Price Vendor', compute='compute_price_vendor') so_line_id = fields.Many2one('sale.order.line', string='ID SO Line') so_id = fields.Many2one('sale.order', string='SO') indent = fields.Boolean(string='Indent', help='centang ini jika barang indent') is_ltc = fields.Boolean(string='Sudah di LTC', default=False, help='centang ini jika barang sudah di LTC') note = fields.Char(string='Note') sale_automatic_id = fields.Many2one('sale.order', string='SO') qty_reserved = fields.Float(string='Qty Reserved', compute='_compute_qty_reserved') delete_line = fields.Boolean(string='Delete', default=False, help='centang ini jika anda ingin menghapus line ini') is_edit_product_qty = fields.Boolean(string='Is Edit Product Qty', compute='_compute_is_edit_product_qty') def _prepare_stock_move_vals(self, picking, price_unit, product_uom_qty, product_uom): self.ensure_one() product = self.product_id.with_context(lang=self.order_id.dest_address_id.lang or self.env.user.lang) description_picking = product._get_description(self.order_id.picking_type_id) if self.product_description_variants: description_picking += "\n" + self.product_description_variants date_planned = self.date_planned or self.order_id.date_planned if self.so_id: sale_id = self.so_id.id else: sale_id = self.so_line_id.order_id.id return { # truncate to 2000 to avoid triggering index limit error # TODO: remove index in master? 'name': (self.name or '')[:2000], 'product_id': self.product_id.id, 'date': date_planned, 'date_deadline': date_planned + relativedelta(days=self.order_id.company_id.po_lead), 'location_id': self.order_id.partner_id.property_stock_supplier.id, 'location_dest_id': (self.orderpoint_id and not (self.move_ids | self.move_dest_ids)) and self.orderpoint_id.location_id.id or self.order_id._get_destination_location(), 'picking_id': picking.id, 'partner_id': self.order_id.dest_address_id.id, 'move_dest_ids': [(4, x) for x in self.move_dest_ids.ids], 'state': 'draft', 'purchase_line_id': self.id, 'company_id': self.order_id.company_id.id, 'price_unit': price_unit, 'picking_type_id': self.order_id.picking_type_id.id, 'group_id': self.order_id.group_id.id, 'origin': self.order_id.name, 'description_picking': description_picking, 'propagate_cancel': self.propagate_cancel, 'warehouse_id': self.order_id.picking_type_id.warehouse_id.id, 'product_uom_qty': product_uom_qty, 'product_uom': product_uom.id, 'sale_id': sale_id, } # @api.constrains('price_unit') def constrains_purchase_price(self): for line in self: matches_so = self.env['purchase.order.sales.match'].search([ ('purchase_order_id', '=', line.order_id.id), ('product_id', '=', line.product_id.id), ]) matches_so.sale_line_id.purchase_price = line.price_unit @api.constrains('product_qty') def constrains_product_qty(self): for line in self: qty_po = 0 matches_so = self.env['purchase.order.sales.match'].search([ ('purchase_order_id', '=', line.order_id.id), ('product_id', '=', line.product_id.id), ]) if not matches_so: continue for matches in matches_so: qty_po += matches.qty_po if qty_po == line.product_qty: continue oldest_date_order = min(matches_so.mapped('sale_id.date_order')) oldest_matches = matches_so.filtered(lambda x: x.sale_id.date_order == oldest_date_order) matches_to_remove = matches_so - oldest_matches if matches_to_remove: matches_to_remove.unlink() def unlink(self): for line in self: mathces_so = self.env['purchase.order.sales.match'].search([ ('purchase_order_id', '=', line.order_id.id), ('product_id', '=', line.product_id.id), ]) mathces_so.unlink() return super(PurchaseOrderLine, self).unlink() def _compute_is_edit_product_qty(self): for line in self: if line.order_id.state in ['draft']: is_valid = True else: is_valid = False line.is_edit_product_qty = is_valid # @api.constrains('product_qty') # def change_qty_po_and_qty_demand(self): # for line in self: # if line.order_id.state in ['draft', 'cancel'] and len(line.order_id.picking_ids) == 0: # continue # for stock_picking in line.order_id.picking_ids: # picking = self.env['stock.move'].search([ # ('picking_id.purchase_id', '=', line.order_id.id), # ('product_id', '=', line.product_id.id) # ]) # if picking: # picking.write({ # 'product_uom_qty': line.product_qty # }) def _compute_qty_reserved(self): for line in self: sale_line = self.env['sale.order.line'].search([ ('product_id', '=', line.product_id.id), ('order_id', '=', line.order_id.sale_order_id.id) ]) reserved_qty = sum(line.qty_reserved for line in sale_line) line.qty_reserved = reserved_qty # so_line.qty_reserved (compute) # so_line.qty_reserved = get from picking_ids where type outgoing and prodid = line.prodid # po_line.qty_reserved = cek dulu apakah ada relasi ke sale order. Jika ada maka ambil sesuai yang ada di sale order (so_line.qty_reserved), # jika tidak maka 0 def suggest_purchasing(self): for line in self: if line.product_id.qty_available_bandengan + line.qty_reserved < line.product_qty: line.suggest = 'harus beli' else: line.suggest = 'masih cukup' def compute_price_vendor(self): for line in self: purchase_pricelist = self.env['purchase.pricelist'].search([ ('product_id', '=', line.product_id.id), ('vendor_id', '=', line.order_id.partner_id.id) ], limit=1) if purchase_pricelist: price_vendor = format(purchase_pricelist.product_price, ".2f") price_vendor = float(price_vendor) line.price_vendor = price_vendor else: line.price_vendor = 0 def _compute_qty_stock(self): for line in self: line.qty_available = line.product_id.qty_available_bandengan line.qty_onhand = line.product_id.qty_onhand_bandengan line.qty_incoming = line.product_id.qty_incoming_bandengan line.qty_outgoing = line.product_id.qty_outgoing_bandengan @api.onchange('product_id') def _onchange_product_custom(self): self._compute_qty_stock() @api.onchange('product_id','product_qty', 'product_uom') def _onchange_quantity(self): res = super(PurchaseOrderLine, self)._onchange_quantity() purchase_pricelist = self.env['purchase.pricelist'].search([ ('product_id', '=', self.product_id.id), ('vendor_id', '=', self.partner_id.id), ], limit=1) price_unit = purchase_pricelist.product_price if not price_unit: product_supplierinfo = self.env['product.supplierinfo'].search([ ('product_tmpl_id', '=', self.product_id.product_tmpl_id.id), ('name', '=', self.partner_id.id) ], limit=1) price_unit = product_supplierinfo.price price_unit, taxes = self._get_valid_purchase_price(purchase_pricelist) self.price_unit = price_unit if purchase_pricelist.taxes_product_id or purchase_pricelist.taxes_system_id: self.taxes_id = taxes return res def _get_valid_purchase_price(self, purchase_price): price = 0 taxes = None human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min if system_last_update > human_last_update: price = purchase_price.system_price taxes = [purchase.taxes_system_id.id for purchase in purchase_price ] else: price = purchase_price.product_price taxes = [purchase.taxes_product_id.id for purchase in purchase_price ] return price, taxes def compute_item_margin(self): sum_so_margin = sum_sales_price = sum_margin = 0 for line in self: if not line.product_id or line.product_id.type == 'service' or not self.order_id.sale_order_id: line.so_item_margin = 0 line.so_item_percent_margin = 0 line.item_margin = 0 line.item_percent_margin = 0 continue 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') line.so_item_margin = sale_order_line.item_margin line.so_item_percent_margin = sale_order_line.item_percent_margin 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 real_item_percent_margin = round((real_item_margin/sales_price), 2) * 100 line.item_margin = real_item_margin line.item_percent_margin = real_item_percent_margin sum_margin += real_item_margin def compute_delivery_amt_line(self): for line in self: if line.product_id.type == 'product': contribution = round((line.price_total / line.order_id.amount_total_without_service), 2) delivery_amt = line.order_id.delivery_amount line.delivery_amt_line = delivery_amt * contribution else: line.delivery_amt_line = 0