from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.misc import formatLang, get_lang import warnings class SaleOrder(models.Model): _inherit = "sale.order" total_margin = fields.Float( 'Total Margin', compute='compute_total_margin', help="Total Margin in Sales Order Header") total_percent_margin = fields.Float( 'Total Percent Margin', compute='compute_total_margin', help="Total % Margin in Sales Order Header") approval_status = fields.Selection([ ('pengajuan1', 'Approval Manager'), ('pengajuan2', 'Approval Pimpinan'), ('approved', 'Approved'), ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3) carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') have_visit_service = fields.Boolean(string='Have Visit Service', help='To compute is customer get visit service', compute='_compute_have_visit_service') delivery_amt = fields.Float('Delivery Amt') shipping_cost_covered = fields.Selection([ ('indoteknik', 'Indoteknik'), ('customer', 'Customer') ], string='Shipping Covered by', help='Siapa yang menanggung biaya ekspedisi?', copy=False) shipping_paid_by = fields.Selection([ ('indoteknik', 'Indoteknik'), ('customer', 'Customer') ], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False) sales_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) def _compute_have_visit_service(self): limit = 20000000 self.have_visit_service = False if self.amount_total > limit: self.have_visit_service = True def sale_order_approve(self): # raise UserError("Bisa langsung Confirm") for order in self: if order.state == 'cancel' or order.state == 'done' or order.state == 'sale': raise UserError("Status harus draft atau sent") if not order.sales_tax_id: raise UserError("Tax di Header harus diisi") if not order.carrier_id: raise UserError("Shipping Method harus diisi") # approval1 = approval2 = 0 for line in order.order_line: if not line.product_id or line.product_id.type == 'service': continue if line.product_id.id == 232383: raise UserError(_('Tidak bisa Confirm menggunakan Produk Sementara')) if not line.vendor_id or not line.purchase_price: raise UserError(_('Isi Vendor dan Harga Beli sebelum Request Approval')) # if line.item_percent_margin <= 15 and not self.env.user.is_leader: # akbar or tyas # approval2 += 1 # elif line.item_percent_margin <= 25 and not self.env.user.is_leader and not self.env.user.is_sales_manager: # approval1 += 1 # if approval2 > 0: # order.approval_status = 'pengajuan2' # elif approval1 > 0: # order.approval_status = 'pengajuan1' # else: # raise UserError("Bisa langsung Confirm") if order.total_percent_margin <= 15 and not self.env.user.is_leader: order.approval_status = 'pengajuan2' elif order.total_percent_margin <= 25 and not self.env.user.is_leader and not self.env.user.is_sales_manager: order.approval_status = 'pengajuan1' else: raise UserError("Bisa langsung Confirm") def action_cancel(self): self.approval_status = False return super(SaleOrder, self).action_cancel() def action_confirm(self): res = super(SaleOrder, self).action_confirm() for order in self: if not order.sales_tax_id: raise UserError("Tax di Header harus diisi") if not order.carrier_id: raise UserError("Shipping Method harus diisi") # approval1 = approval2 = 0 for line in order.order_line: if not line.product_id or line.product_id.type == 'service': continue if line.product_id.id == 232383: raise UserError(_('Tidak bisa Confirm menggunakan Produk Sementara')) if not line.vendor_id or not line.purchase_price or not line.purchase_tax_id: raise UserError(_('Isi Vendor, Harga Beli, dan Tax sebelum Request Approval')) # if line.item_percent_margin <= 15 and not self.env.user.is_leader: # approval2 += 1 # elif line.item_percent_margin <= 25 and not self.env.user.is_leader and not self.env.user.is_sales_manager: # approval1 += 1 # if approval2 > 0: # raise UserError("Harus diapprove oleh Pimpinan") # elif approval1 > 0: # raise UserError("Harus diapprove oleh Manager") # order.approval_status = 'approved' if order.total_percent_margin <= 15 and not self.env.user.is_leader: raise UserError("Harus diapprove oleh Pimpinan") elif order.total_percent_margin <= 25 and not self.env.user.is_leader and not self.env.user.is_sales_manager: raise UserError("Harus diapprove oleh Manager") else: order.approval_status = 'approved' return res def compute_total_margin(self): for order in self: total_margin = total_percent_margin = sum_sales_price = 0 for line in order.order_line: if not line.product_id: order.total_margin = 0 order.total_percent_margin = 0 continue total_margin += line.item_margin sales_price = line.price_reduce_taxexcl * line.product_uom_qty if line.order_id.shipping_cost_covered == 'indoteknik': sales_price -= line.delivery_amt_line sum_sales_price += sales_price order.total_margin = total_margin if order.amount_untaxed > 0: total_percent_margin = round((total_margin / sum_sales_price), 2) * 100 order.total_percent_margin = total_percent_margin 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 @api.onchange('sales_tax_id') def onchange_sales_tax_id(self): for line in self.order_line: line.product_id_change() class SaleOrderLine(models.Model): _inherit = 'sale.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") vendor_id = fields.Many2one( 'res.partner', string='Vendor', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, change_default=True, index=True, tracking=1, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", ) purchase_price = fields.Float('Purchase', required=True, digits='Product Price', default=0.0) purchase_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) delivery_amt_line = fields.Float('DeliveryAmtLine', compute='compute_delivery_amt_line') def compute_item_margin(self): for line in self: if not line.product_id or line.product_id.type == 'service' \ or line.price_unit <= 0 or line.product_uom_qty <= 0 \ or not line.vendor_id: line.item_margin = 0 line.item_percent_margin = 0 continue # calculate margin without tax sales_price = line.price_reduce_taxexcl * line.product_uom_qty # minus with delivery if covered by indoteknik if line.order_id.shipping_cost_covered == 'indoteknik': sales_price -= line.delivery_amt_line # sales_price -= round((line.order_id.delivery_amt / line.order_id.count_line_product), 2) purchase_price = line.purchase_price if line.purchase_tax_id.price_include: purchase_price = line.purchase_price / 1.11 purchase_price = purchase_price * line.product_uom_qty margin_per_item = sales_price - purchase_price line.item_margin = margin_per_item # if sales_price > 0: line.item_percent_margin = round((margin_per_item / sales_price), 2) * 100 @api.onchange('vendor_id') def onchange_vendor_id(self): if not self.product_id or self.product_id.type == 'service': return purchase_price = self.env['purchase.pricelist'].search( [('vendor_id', '=', self.vendor_id.id), ('product_id', '=', self.product_id.id)], limit=1) self.purchase_price = purchase_price.product_price self.purchase_tax_id = 22 @api.onchange('product_id') def product_id_change(self): super(SaleOrderLine, self).product_id_change() for line in self: if line.product_id and line.product_id.type == 'product': purchase_price = self.env['purchase.pricelist'].search( [('product_id', '=', self.product_id.id)], limit=1, order='product_price ASC') line.vendor_id = purchase_price.vendor_id line.tax_id = line.order_id.sales_tax_id def compute_delivery_amt_line(self): for line in self: contribution = round((line.price_total / line.order_id.amount_total), 2) delivery_amt = line.order_id.delivery_amt line.delivery_amt_line = delivery_amt * contribution @api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id') def _onchange_discount(self): if not (self.product_id and self.product_uom and self.order_id.partner_id and self.order_id.pricelist_id and self.order_id.pricelist_id.discount_policy == 'without_discount' and self.env.user.has_group('product.group_discount_per_so_line')): return self.discount = 0.0 product = self.product_id.with_context( lang=self.order_id.partner_id.lang, partner=self.order_id.partner_id, quantity=self.product_uom_qty, date=self.order_id.date_order, pricelist=self.order_id.pricelist_id.id, uom=self.product_uom.id, fiscal_position=self.env.context.get('fiscal_position') ) product_context = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order, uom=self.product_uom.id) price, rule_id = self.order_id.pricelist_id.with_context(product_context).get_product_price_rule( self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) new_list_price, currency = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) new_list_price = product.web_price if new_list_price != 0: if self.order_id.pricelist_id.currency_id != currency: # we need new_list_price in the same currency as price, which is in the SO's pricelist's currency new_list_price = currency._convert( new_list_price, self.order_id.pricelist_id.currency_id, self.order_id.company_id or self.env.company, self.order_id.date_order or fields.Date.today()) discount = (new_list_price - price) / new_list_price * 100 if (discount > 0 and new_list_price > 0) or (discount < 0 and new_list_price < 0): self.discount = discount def _get_display_price(self, product): # TO DO: move me in master/saas-16 on sale.order # awa: don't know if it's still the case since we need the "product_no_variant_attribute_value_ids" field now # to be able to compute the full price # it is possible that a no_variant attribute is still in a variant if # the type of the attribute has been changed after creation. no_variant_attributes_price_extra = [ ptav.price_extra for ptav in self.product_no_variant_attribute_value_ids.filtered( lambda ptav: ptav.price_extra and ptav not in product.product_template_attribute_value_ids ) ] if no_variant_attributes_price_extra: product = product.with_context( no_variant_attributes_price_extra=tuple(no_variant_attributes_price_extra) ) if self.order_id.pricelist_id.discount_policy == 'with_discount': return product.with_context(pricelist=self.order_id.pricelist_id.id, uom=self.product_uom.id).price product_context = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order, uom=self.product_uom.id) final_price, rule_id = self.order_id.pricelist_id.with_context(product_context).get_product_price_rule(product or self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) base_price, currency = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) base_price = product.web_price if currency != self.order_id.pricelist_id.currency_id: base_price = currency._convert( base_price, self.order_id.pricelist_id.currency_id, self.order_id.company_id or self.env.company, self.order_id.date_order or fields.Date.today()) # negative discounts (= surcharge) are included in the display price return max(base_price, final_price)