from odoo import models, api, fields, tools from odoo.exceptions import UserError from datetime import datetime import logging, math _logger = logging.getLogger(__name__) class AutomaticPurchase(models.Model): _name = 'automatic.purchase' _order = 'id desc' _inherit = ['mail.thread'] _rec_name = 'number' number = fields.Char(string='Document No', index=True, copy=False, readonly=True) date_doc = fields.Date(string='Date', readonly=True, help='Isi tanggal hari ini') description = fields.Char(string='Description', help='bebas isi nya apa') purchase_lines = fields.One2many('automatic.purchase.line', 'automatic_purchase_id', string='Lines', auto_join=True) notification = fields.Char(string='Notification') is_po = fields.Boolean(string='Is PO') purchase_match = fields.One2many('automatic.purchase.match', 'automatic_purchase_id', string='PO Matches', auto_join=True) vendor_id = fields.Many2one('res.partner', string='Vendor', help='boleh kosong, jika diisi, maka hanya keluar data untuk vendor tersebut') responsible_id = fields.Many2one('res.users', string='Responsible') apo_type = fields.Selection([ ('regular', 'Regular Fulfill SO'), ('reordering', 'Reordering Rule'), ], string='Type', tracking=3) sales_match = fields.One2many('automatic.purchase.sales.match', 'automatic_purchase_id', string='SO Matches', auto_join=True) purchasing_job_match = fields.One2many('sync.purchasing.job', 'automatic_purchase_id', string='Purchasing Job Matches', auto_join=True) total_qty_line = fields.Float(string='Total Qty Line', compute='_compute_total_qty') total_qty_so = fields.Float(string='Total Qty SO', compute='_compute_total_qty') @api.constrains('apo_type') def onchange_apo_type(self): if self.apo_type == 'reordering': self.responsible_id = 19 elif self.apo_type == 'regular': self.responsible_id = self.env.user.id def _compute_total_qty(self): for data in self: qty_line = qty_so = 0 for line in self.purchase_lines: qty_line = qty_line + line.qty_purchase for line in self.sales_match: qty_so = qty_so + line.qty_so data.total_qty_line = qty_line data.total_qty_so = qty_so @api.model def create(self, vals): vals['number'] = self.env['ir.sequence'].next_by_code('automatic.purchase') or '0' result = super(AutomaticPurchase, self).create(vals) return result def validate_so_is_po(self): # TODO user can't create po if in so exist matches po sale_ids = self.env['automatic.purchase.sales.match'].search([ ('automatic_purchase_id', '=', self.id), ]) names = [] for sale in sale_ids: names.append(sale.sale_id.name) count = len(sale_ids) if count > 0: raise UserError('Ada sekitar %s SO Yang sudah create PO, berikut SO nya: %s' % (count, ', '.join(names))) def unlink_note_pj(self): product = self.purchase_lines.mapped('product_id') pj_state = self.env['purchasing.job.state'].search([ ('purchasing_job_id', 'in', product.ids) ]) for line in pj_state: line.unlink() def create_po_from_automatic_purchase(self): if not self.purchase_lines: raise UserError('Tidak ada Lines, belum bisa create PO') if self.is_po: raise UserError('Sudah pernah di create PO') current_time = datetime.now() self.unlink_note_pj() vendor_ids = self.env['automatic.purchase.line'].read_group( [('automatic_purchase_id', '=', self.id), ('partner_id', '!=', False)], fields=['partner_id'], groupby=['partner_id'] ) counter = 0 for vendor in vendor_ids: self.create_po_for_vendor(vendor['partner_id'][0]) # param_header = { # 'partner_id': vendor['partner_id'][0], # 'currency_id': 12, # 'user_id': self.env.user.id, # 'company_id': 1, # indoteknik dotcom gemilang # 'picking_type_id': 28, # indoteknik bandengan receipts # 'date_order': current_time, # 'note_description': 'Automatic PO' # } # products_vendors = self.env['automatic.purchase.line'].search([ # ('automatic_purchase_id', '=', self.id), # ('partner_id', '=', vendor['partner_id'][0]), # ('qty_purchase', '>', 0) # ], order='brand_id') # counter += 1 # new_po = self.env['purchase.order'].create([param_header]) # new_po.name = new_po.name + "/A/" + str(counter) # self.env['automatic.purchase.match'].create([{ # 'automatic_purchase_id': self.id, # 'order_id': new_po.id # }]) # self.env.cr.commit() # count = 0 # for product in products_vendors: # if count == 20: # self.create_purchase_order_sales_match(new_po) # if count == 20: # counter += 1 # new_po = self.env['purchase.order'].create([param_header]) # new_po.name = new_po.name + "/B/" + str(counter) # self.env['automatic.purchase.match'].create([{ # 'automatic_purchase_id': self.id, # 'order_id': new_po.id # }]) # self.create_purchase_order_sales_match(new_po) # self.env.cr.commit() # count += 1 # qty_available = ( # product.product_id.qty_onhand_bandengan + # product.product_id.qty_incoming_bandengan - # product.product_id.outgoing_qty # ) # suggest = 'harus beli' if qty_available <= product.qty_purchase else 'masih cukup' # param_line = { # 'order_id': new_po.id, # 'product_id': product.product_id.id, # 'product_qty': product.qty_purchase, # 'qty_available_store': qty_available, # 'suggest': suggest, # 'product_uom_qty': product.qty_purchase, # 'price_unit': product.last_price, # } # new_line = self.env['purchase.order.line'].create([param_line]) # product.current_po_id = new_po.id # product.current_po_line_id = new_line.id # self.create_purchase_order_sales_match(new_po) self.notification = 'PO Created successfully' self.is_po = True def check_qty_po(self): for lines in self.purchase_lines: purchasing_job = self.env['v.purchasing.job'].search([ ('product_id', 'in', [lines.product_id.id]) ]) for sale_id in lines.automatic_purchase_id.sales_match.mapped('sale_id.id'): po = self.env['purchase.order'].search([ ('product_id', 'in', [lines.product_id.id]), ('order_sales_match_line.sale_id', '=', sale_id), ('state', 'not in', ['cancel']), ('from_apo', '=', True) ]) if not purchasing_job: raise UserError('Beberapa product sudah terbuat po dan sudah terconfirm') qty_pj = 0 qty_outgoing_pj = 0 for line in po.order_line: qty_pj += line.product_qty + lines.qty_purchase qty_outgoing_pj += line.qty_outgoing id_po = [] if qty_pj > qty_outgoing_pj: id_po.append(po.name) if qty_pj > qty_outgoing_pj: raise UserError('Qty yang anda beli lebih dari qty outgoing. %s' %id_po) def create_po_for_vendor(self, vendor_id): current_time = datetime.now() name = "/PJ/" if not self.apo_type == 'reordering' else "/A/" PRODUCT_PER_PO = 20 auto_purchase_line = self.env['automatic.purchase.line'] config = self.env['apo.domain.config'].search([ ('vendor_id', '=', vendor_id) ], limit=1) base_domain = [ ('automatic_purchase_id', '=', self.id), ('partner_id', '=', vendor_id), ('qty_purchase', '>', 0) ] # Kalau vendor punya brand spesial → bikin domain sesuai config if config and config.is_special: special_brand_domain = base_domain + [('brand_id', 'in', config.brand_ids.ids)] self._create_po_for_domain( vendor_id, special_brand_domain, name, PRODUCT_PER_PO, current_time, config.payment_term_id, special=config.is_special ) # Regular domain (selain brand spesial) regular_domain = base_domain if config and config.is_special and config.brand_ids: regular_domain = base_domain + [('brand_id', 'not in', config.brand_ids.ids)] self._create_po_for_domain( vendor_id, regular_domain, name, PRODUCT_PER_PO, current_time, config.payment_term_id ) def _create_po_for_domain(self, vendor_id, domain, name, PRODUCT_PER_PO, current_time, payment_term_id, special=False): auto_purchase_line = self.env['automatic.purchase.line'] products_len = auto_purchase_line.search_count(domain) page = math.ceil(products_len / PRODUCT_PER_PO) for i in range(page): # Buat header PO param_header = { 'partner_id': vendor_id, 'currency_id': 12, 'user_id': self.env.user.id, 'company_id': 1, 'picking_type_id': 28, 'date_order': current_time, 'from_apo': True, 'note_description': 'Automatic PO', 'show_description': False if vendor_id == 5571 else True, } new_po = self.env['purchase.order'].create(param_header) # Set payment term new_po.payment_term_id = payment_term_id.id if special else ( new_po.partner_id.property_supplier_payment_term_id ) new_po.name = new_po.name + name + str(i + 1) self.env['automatic.purchase.match'].create([{ 'automatic_purchase_id': self.id, 'order_id': new_po.id }]) # Ambil lines lines = auto_purchase_line.search( domain, offset=i * PRODUCT_PER_PO, limit=PRODUCT_PER_PO ) # Pre-fetch sales_match biar ga search per line sales_matches = self.env['automatic.purchase.sales.match'].search([ ('automatic_purchase_id', '=', self.id), ('product_id', 'in', lines.mapped('product_id').ids), ]) match_map = {sm.product_id.id: sm for sm in sales_matches} for line in lines: product = line.product_id sales_match = match_map.get(product.id) param_line = { 'order_id': new_po.id, 'product_id': product.id, 'product_qty': line.qty_purchase, 'qty_available_store': product.qty_available_bandengan, 'suggest': product._get_po_suggest(line.qty_purchase), 'product_uom_qty': line.qty_purchase, 'price_unit': line.last_price, 'ending_price': line.last_price, 'taxes_id': [(6, 0, [line.taxes_id.id])] if line.taxes_id else False, 'so_line_id': sales_match.sale_line_id.id if sales_match else None, 'so_id': sales_match.sale_id.id if sales_match else None, 'show_description': False if vendor_id == 5571 else True, } new_po_line = self.env['purchase.order.line'].create(param_line) line.current_po_id = new_po.id line.current_po_line_id = new_po_line.id self.create_purchase_order_sales_match(new_po) def update_purchase_price_so_line(self, apo): sales_match = self.env['automatic.purchase.sales.match'].search([ ('automatic_purchase_id', '=', self.id), ('product_id', '=', apo.product_id.id), ]) for sales in sales_match: sales.sale_line_id.purchase_price = apo.last_price def create_purchase_order_sales_match(self, purchase_order): matches_so_product_ids = [line.product_id.id for line in purchase_order.order_line] matches_so = self.env['v.sale.notin.matchpo'].search([ ('automatic_purchase_id', '=', self.id), ('sale_line_id.product_id', 'in', matches_so_product_ids), ]) sale_ids_set = set() sale_ids_name = set() retur_cache = {} incoming_cache = {} for sale_order in matches_so: exist = self.env['purchase.order.sales.match'].search([ ('product_id', '=', sale_order.product_id.id), ('sale_line_id', '=', sale_order.sale_line_id.id), ('sale_id', '=', sale_order.sale_id.id), ('purchase_order_id.state', '!=', 'cancel'), ]) skip_line = False sale_line_id = sale_order.sale_line_id.id if sale_line_id not in incoming_cache: qty_incoming = 0 for existing in exist: if existing.purchase_order_id.state in ['done', 'purchase']: incoming_moves = self.env['stock.move'].search([ ('reference', 'ilike', 'BU/INPUT'), ('state', 'not in', ['done','cancel']), ('product_id', '=', existing.product_id.id), ('purchase_line_id', '=', existing.purchase_line_id.id), ]) qty_incoming += sum(incoming_moves.mapped('product_uom_qty')) incoming_cache[sale_line_id] = qty_incoming qty_need = sale_order.sale_line_id.product_uom_qty if incoming_cache[sale_line_id] >= qty_need: skip_line = True sale_line_id = sale_order.sale_line_id.id if sale_line_id not in retur_cache: fully_received = True for existing in exist: if existing.purchase_order_id.state in ['done', 'purchase']: if existing.purchase_line_id.qty_received != existing.purchase_line_id.product_qty: fully_received = False break retur_cache[sale_line_id] = fully_received if retur_cache[sale_line_id] and exist: skip_line = True if skip_line: continue stock_move = self.env['stock.move'].search([ ('reference', 'ilike', 'BU/PICK'), ('state', 'in', ['confirmed','waiting','partially_available']), ('product_id', '=', sale_order.product_id.id), ('sale_line_id', '=', sale_order.sale_line_id.id), ]) if not stock_move: continue # @stephan skip so line yang sudah pernah ada di purchase order sales match sebelumnya salesperson_name = sale_order.sale_id.user_id.name sale_id_with_salesperson = f"{sale_order.sale_id.name} - {salesperson_name}" sale_ids_set.add(sale_id_with_salesperson) sale_ids_name.add(sale_order.sale_id.name) margin_item = sale_order.sale_line_id.item_margin / sale_order.qty_so if sale_order.qty_so else 0 margin_item = margin_item * sale_order.qty_po matches_so_line = { 'purchase_order_id': purchase_order.id, 'sale_id': sale_order.sale_id.id, 'sale_line_id': sale_order.sale_line_id.id, 'picking_id': sale_order.picking_id.id, 'move_id': sale_order.move_id.id, 'partner_id': sale_order.partner_id.id, 'partner_invoice_id': sale_order.partner_invoice_id.id, 'salesperson_id': sale_order.salesperson_id.id, 'product_id': sale_order.product_id.id, 'qty_so': sale_order.qty_so, 'qty_po': sale_order.qty_po, 'margin_so': sale_order.sale_line_id.item_percent_margin, 'margin_item': margin_item } po_matches_so_line = self.env['purchase.order.sales.match'].create([matches_so_line]) sale_ids_str = ','.join(sale_ids_set) sale_ids_str_name = ','.join(sale_ids_name) purchase_order.sale_order = sale_ids_str_name purchase_order.notes = sale_ids_str self.create_sales_order_purchase_match(purchase_order) def create_sales_order_purchase_match(self, purchase_order): #TODO add matches po to sales order by automatic_purchase.sales.match matches_po_product_ids = [line.product_id.id for line in purchase_order.order_line] sales_match_line = self.env['automatic.purchase.sales.match'].search([ ('automatic_purchase_id', '=', self.id), ('sale_line_id.product_id', 'in', matches_po_product_ids), ]) for sales_match in sales_match_line: purchase_line = self.env['automatic.purchase.line'].search([ ('automatic_purchase_id', '=', self.id), ('product_id', 'in', [sales_match.product_id.id]), ], limit=1) matches_po_line = { 'sales_order_id' : sales_match.sale_id.id, 'purchase_order_id' : purchase_line.current_po_id.id, 'purchase_line_id' : purchase_line.current_po_line_id.id, 'product_id' : sales_match.product_id.id, 'qty_so' : sales_match.qty_so, 'qty_po' : sales_match.qty_po, } sales_order_purchase_match = self.env['sales.order.purchase.match'].create([matches_po_line]) def generate_regular_purchase(self, jobs): current_time = datetime.utcnow() automatic_purchase = self.env['automatic.purchase'].create([{ 'apo_type': 'regular', 'date_doc': current_time, }]) automatic_purchase.responsible_id = self.env.user.id automatic_purchase.date_doc = datetime.utcnow() #TODO must add order by for fifo mechanism #change the view of v.purchasing.job and use order by in query count = 0 for job in jobs: qty_purchase = job.outgoing - (job.onhand + job.incoming) qty_available = (job.onhand + job.incoming) - job.outgoing domain = [ ('product_id.id', '=', job.product_id.id), ] orderby = 'count_trx_po desc, count_trx_po_vendor desc' purchase_pricelist = self.env['purchase.pricelist'].search(domain, order=orderby, limit=1) vendor_id = purchase_pricelist.vendor_id taxes = '' price, taxes = automatic_purchase._get_valid_purchase_price(purchase_pricelist) last_po_line = self.env['purchase.order.line'].search([('product_id', '=', job.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) self.env['automatic.purchase.line'].create([{ 'automatic_purchase_id': automatic_purchase.id, 'product_id': job.product_id.id, 'qty_purchase': qty_purchase, 'qty_available': qty_available, 'partner_id': vendor_id.id, 'last_price': price, 'taxes_id': taxes, 'subtotal': qty_purchase * price, 'last_order_id': last_po_line.order_id.id, 'last_orderline_id': last_po_line.id, 'brand_id': job.product_id.product_tmpl_id.x_manufacture.id }]) count += 1 _logger.info('Create Automatic Purchase Line %s' % job.product_id.name) automatic_purchase.notification = "Automatic PO Created %s Lines" % count automatic_purchase._create_sales_matching() automatic_purchase._create_sync_purchasing_job(jobs) return automatic_purchase.id def _create_sales_matching(self): for line in self.purchase_lines: domain = [ ('product_id', '=', line.product_id.id) ] sales = self.env['v.sales.outstanding'].search(domain, order='sale_order_create_date desc') for sale in sales: existing_match = self.env['automatic.purchase.sales.match'].search([ ('automatic_purchase_id', '=', self.id), ('sale_id', '=', sale.sale_id.id), ('sale_line_id', '=', sale.sale_line_id.id), ('product_id', '=', sale.product_id.id), ]) # price_so = self.env['sale.order.line'].search([ # ('id', '=', sale.sale_line_id.id), # ]) if existing_match: continue self.env['automatic.purchase.sales.match'].create([{ 'automatic_purchase_id': self.id, 'sale_id': sale.sale_id.id, 'sale_line_id': sale.sale_line_id.id, 'picking_id': sale.picking_id.id, 'move_id': sale.move_id.id, 'partner_id': sale.partner_id.id, 'partner_invoice_id': sale.partner_invoice_id.id, 'salesperson_id': sale.salesperson_id.id, 'product_id': sale.product_id.id, 'qty_so': sale.outgoing, 'qty_po': line.qty_purchase, 'purchase_price': sale.sale_line_id.purchase_price, 'purchase_tax_id': sale.sale_line_id.purchase_tax_id.id if sale.sale_line_id.purchase_tax_id.id else None, # 'purchase_price': price_so.purchase_price, # 'purchase_tax_id': price_so.purchase_tax_id.id if price_so.purchase_tax_id.id else None, }]) def _create_sync_purchasing_job(self, jobs): date = datetime.utcnow() for line in jobs: self.env['sync.purchasing.job'].create([{ 'automatic_purchase_id': self.id, 'brand': line.brand, 'item_code': line.item_code, 'product_id': line.product_id.id, 'onhand': line.onhand, 'incoming': line.incoming, 'outgoing': line.outgoing, 'action': line.action, 'date': date }]) def generate_automatic_purchase(self): # for reordering rule only if self.apo_type == 'regular': raise UserError('Tombol ini hanya untuk Reordering Rule') if self.purchase_lines: raise UserError('Sudah digenerate sebelumnya, hapus line terlebih dahulu') query = [ ('product_min_qty', '>', 0), ('product_id.x_manufacture.user_id.id', '=', self.responsible_id.id) ] orderpoints = self.env['stock.warehouse.orderpoint'].search(query) count = 0 for point in orderpoints: # _logger.info('test %s' % point.product_id.name) if point.product_id.qty_available_bandengan > point.product_min_qty: continue # qty_purchase = point.product_max_qty - point.product_id.qty_incoming_bandengan - point.product_id.qty_available_bandengan qty_purchase = point.product_max_qty - point.product_id.qty_available_bandengan po_line = self.env['purchase.order.line'].search([('product_id', '=', point.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) if self.vendor_id: purchase_price = self.env['purchase.pricelist'].search([ ('product_id', '=', point.product_id.id), # ('product_id.x_manufacture.user_id.id', '=', self.responsible_id.id), ('vendor_id', '=', self.vendor_id.id) ], order='count_trx_po desc, count_trx_po_vendor desc', limit=1) else: purchase_price = self.env['purchase.pricelist'].search([ ('product_id', '=', point.product_id.id), # ('product_id.x_manufacture.user_id.id', '=', self.responsible_id.id), ], order='count_trx_po desc, count_trx_po_vendor desc', limit=1) vendor_id = purchase_price.vendor_id.id price, taxes = self._get_valid_purchase_price(purchase_price) if self.vendor_id and self.vendor_id.id != vendor_id: continue self.env['automatic.purchase.line'].create([{ 'automatic_purchase_id': self.id, 'product_id': point.product_id.id, 'qty_purchase': qty_purchase, 'qty_min': point.product_min_qty, 'qty_max': point.product_max_qty, 'qty_available': point.product_id.qty_available_bandengan, # 'partner_id': po_line.order_id.partner_id.id, # 'last_price': po_line.price_unit, 'partner_id': vendor_id, 'last_price': price, 'taxes_id': taxes, 'subtotal': qty_purchase * price, 'last_order_id': po_line.order_id.id, 'last_orderline_id': po_line.id, 'brand_id': point.product_id.product_tmpl_id.x_manufacture.id }]) count += 1 _logger.info('Create Automatic Purchase Line %s' % point.product_id.name) self.notification = "Automatic PO Created %s Lines" % count def _get_valid_purchase_price(self, purchase_price): price = 0 taxes = '' human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min if purchase_price.taxes_product_id.type_tax_use == 'purchase': price = purchase_price.product_price taxes = purchase_price.taxes_product_id.id if system_last_update > human_last_update: if purchase_price.taxes_system_id.type_tax_use == 'purchase': price = purchase_price.system_price taxes = purchase_price.taxes_system_id.id return price, taxes class AutomaticPurchaseLine(models.Model): _name = 'automatic.purchase.line' _description = 'Automatic Purchase Line' _order = 'automatic_purchase_id, id' automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) product_id = fields.Many2one('product.product', string='Product') qty_purchase = fields.Float(string='Qty Purchase') qty_min = fields.Float(string='Qty Min') qty_max = fields.Float(string='Qty Max') qty_available = fields.Float(string='Qty Available') partner_id = fields.Many2one('res.partner', string='Vendor') last_price = fields.Float(string='Last Price') subtotal = fields.Float(string='Subtotal') last_order_id = fields.Many2one('purchase.order', string='Last Order') last_orderline_id = fields.Many2one('purchase.order.line', string='Last Order Line') is_po = fields.Boolean(String='Is PO') current_po_id = fields.Many2one('purchase.order', string='Current') current_po_line_id = fields.Many2one('purchase.order.line', string='Current Line') brand_id = fields.Many2one('x_manufactures', string='Brand') taxes_id = fields.Many2one('account.tax', string='Taxes') @api.onchange('last_price', 'qty_purchase') def _calculate_subtotal(self): for line in self: line.subtotal = line.qty_purchase * line.last_price @api.onchange('product_id') def _onchange_product_id(self): for line in self: purchase_price = self.env['purchase.pricelist'].search([ ('product_id', '=', line.product_id.id), ('vendor_id', '=', line.partner_id.id) ], order='count_trx_po desc, count_trx_po_vendor desc', limit=1) vendor_id = purchase_price.vendor_id.id price, taxes = self._get_valid_purchase_price(purchase_price) line.last_price = price line.taxes_id = taxes line.brand_id = line.product_id.product_tmpl_id.x_manufacture.id line.partner_id = vendor_id def _get_valid_purchase_price(self, purchase_price): price = 0 taxes = 24 human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min #if purchase_price.taxes_product_id.type_tax_use == 'purchase': price = purchase_price.product_price taxes = purchase_price.taxes_product_id.id or 24 if system_last_update > human_last_update: #if purchase_price.taxes_system_id.type_tax_use == 'purchase': price = purchase_price.system_price taxes = purchase_price.taxes_system_id.id or 24 return price, taxes class AutomaticPurchaseMatch(models.Model): _name = 'automatic.purchase.match' _order = 'automatic_purchase_id, id' automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) order_id = fields.Many2one('purchase.order', string='Purchase Order') vendor = fields.Char(string='Vendor', compute='_compute_info_po') total = fields.Float(string='Total', compute='_compute_info_po') def _compute_info_po(self): for match in self: match.vendor = match.order_id.partner_id.name match.total = match.order_id.amount_total class AutomaticPurchaseSalesMatch(models.Model): _name = 'automatic.purchase.sales.match' _order = 'automatic_purchase_id, id' automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) automatic_purchase_line_id = fields.Many2one('automatic.purchase.line', string='Automatic Line') sale_id = fields.Many2one('sale.order', string='SO') sale_line_id = fields.Many2one('sale.order.line', string='SO Line') picking_id = fields.Many2one('stock.picking', string='Picking') move_id = fields.Many2one('stock.move', string='Move') partner_id = fields.Many2one('res.partner', string='Partner') partner_invoice_id = fields.Many2one('res.partner', string='Invoice Partner') salesperson_id = fields.Many2one('res.users', string='Sales') product_id = fields.Many2one('product.product', string='Product') qty_so = fields.Float(string='Qty SO') qty_po = fields.Float(string='Qty PO') purchase_price = fields.Float(string='Purchase Price SO') purchase_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) note_procurement = fields.Char(string='Note Detail', help="Harap diisi jika ada keterangan tambahan dari Procurement, agar dapat dimonitoring") @api.constrains('note_procurement') def note_procurement_to_so_line(self): for rec in self: so_line = self.env['sale.order.line'].search([ ('id', '=', rec.sale_line_id.id), ], limit=1) if so_line.note_procurement == False: so_line.note_procurement = rec.note_procurement if so_line.note_procurement != rec.note_procurement: so_line.note_procurement = rec.note_procurement class SyncPurchasingJob(models.Model): _name = 'sync.purchasing.job' _order = 'automatic_purchase_id, id' automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) product_id = fields.Many2one('product.product', string="Product") brand = fields.Char(string='Brand') item_code = fields.Char(string='Item Code') product = fields.Char(string='Product Name') onhand = fields.Float(string='OnHand') incoming = fields.Float(string="Incoming") outgoing = fields.Float(string="Outgoing") action = fields.Char(string="Status") date = fields.Datetime(string="Date Sync") class SaleNotInMatchPO(models.Model): # created by @stephan for speed up performance while create po from automatic purchase _name = 'v.sale.notin.matchpo' _auto = False _rec_name = 'id' id = fields.Integer() automatic_purchase_id = fields.Many2one('automatic.purchase', string='APO') automatic_purchase_line_id = fields.Many2one('automatic.purchase.line', string='APO Line') sale_id = fields.Many2one('sale.order', string='Sale') sale_line_id = fields.Many2one('sale.order.line', string='Sale Line') picking_id = fields.Many2one('stock.picking', string='Picking') move_id = fields.Many2one('stock.move', string='Move') partner_id = fields.Many2one('res.partner', string='Partner') partner_invoice_id = fields.Many2one('res.partner', string='Partner Invoice') salesperson_id = fields.Many2one('res.user', string='Salesperson') product_id = fields.Many2one('product.product', string='Product') qty_so = fields.Float(string='Qty SO') qty_po = fields.Float(string='Qty PO') create_uid = fields.Many2one('res.user', string='Created By') create_date = fields.Datetime(string='Create Date') write_uid = fields.Many2one('res.user', string='Updated By') write_date = fields.Many2one(string='Updated') purchase_price = fields.Many2one(string='Purchase Price') purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax') note_procurement = fields.Many2one(string='Note Procurement') # 1. yang bug # def init(self): # tools.drop_view_if_exists(self.env.cr, self._table) # self.env.cr.execute(""" # CREATE OR REPLACE VIEW %s AS( # select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id, # apsm.picking_id, apsm.move_id, apsm.partner_id, # apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, # apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, # apsm.purchase_tax_id, apsm.note_procurement # from automatic_purchase_sales_match apsm # where apsm.sale_line_id not in ( # select distinct coalesce(posm.sale_line_id,0) # from purchase_order_sales_match posm # join purchase_order po on po.id = posm.purchase_order_id # where po.state not in ('cancel') # ) # ) # """ % self._table) def init(self): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS( select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id, apsm.picking_id, apsm.move_id, apsm.partner_id, apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, apsm.purchase_tax_id, apsm.note_procurement from automatic_purchase_sales_match apsm ) """ % self._table)