diff options
Diffstat (limited to 'indoteknik_custom/models/purchase_order.py')
| -rwxr-xr-x | indoteknik_custom/models/purchase_order.py | 286 |
1 files changed, 255 insertions, 31 deletions
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index a345b96b..427cc9fc 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -23,6 +23,11 @@ class PurchaseOrder(models.Model): _inherit = 'purchase.order' vcm_id = fields.Many2one('tukar.guling.po', string='Doc VCM', readonly=True, compute='_has_vcm', copy=False) + forecast_line_ids = fields.One2many( + 'purchase.order.forecast.line', + 'order_id', + string='Forecast Coverage', + ) 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) @@ -132,6 +137,196 @@ class PurchaseOrder(models.Model): soo_price = fields.Float('SOO Price', copy=False) soo_discount = fields.Float('SOO Discount', copy=False) soo_tax = fields.Float('SOO Tax', copy=False) + + forecast_raw = fields.Text(string='Forecast Raw', compute='_compute_forecast_raw') + forecast_html = fields.Html(string='Forecast Matches SO', compute='_compute_forecast_html') + + @api.model + def cron_generate_po_forecast(self): + + # two_months_ago = fields.Datetime.now() - relativedelta(months=1) + + # pos = self.env['purchase.order'].search([ + # ('state', 'in', ['purchase','done']), + # '|', + # ('create_date', '>=', two_months_ago), + # ('write_date', '>=', two_months_ago), + # ]) + + pos = self.env['purchase.order'].search([ + ('state','in',['purchase','done']) + ]) + + pos = pos.filtered( + lambda po: any( + m.state not in ['done','cancel'] and 'BU/INPUT' in m.reference + for l in po.order_line + for m in l.move_ids + ) + ) + + itung = len(pos) + + for po in pos: + po._generate_forecast_lines() + + # def _generate_forecast_lines(self): + + # report = self.env['report.stock.report_product_product_replenishment'] + # Forecast = self.env['purchase.order.forecast.line'] + + # for po in self: + + # try: + + # Forecast.search([ + # ('order_id', '=', po.id) + # ]).unlink() + + # products = po.order_line.mapped('product_id') + # if not products: + # continue + + # for product in products: + + # try: + + # data = report._get_report_data( + # product_variant_ids=[product.id] + # ) + + # for l in data.get('lines', []): + + # doc = l.get('document_in') + + # if doc and doc._name == 'purchase.order' and doc.id == po.id: + + # doc_out = l.get('document_out') + + # Forecast.create({ + # 'order_id': po.id, + # 'product_id': product.id, + # 'quantity': l['quantity'], + # 'receipt_date': l.get('receipt_date'), + # 'delivery_date': l.get('delivery_date'), + # 'sale_order': doc_out.display_name if doc_out else '', + # 'sale_order_id': doc_out.id if doc_out else '', + # 'is_late': bool(l.get('is_late')), + # 'replenishment_filled': bool(l.get('replenishment_filled')), + # }) + + # except Exception as line_error: + + # po.message_post( + # body=f"⚠️ Forecast line error: {str(line_error)}" + # ) + + # continue + + # except Exception as po_error: + + # po.message_post( + # body=f"❌ Forecast generation failed: {str(po_error)}" + # ) + + # continue + + @api.depends('forecast_raw') + def _compute_forecast_html(self): + for po in self: + rows = [] + try: + data = json.loads(po.forecast_raw or '[]') + except Exception: + data = [] + + for l in data: + badge = '🟢' if l['replenishment_filled'] else '🔴' + late = '⚠️' if l['is_late'] else '' + + rows.append(f""" + <tr> + <td>{l['product']}</td> + <td style="text-align:right">{l['quantity']}</td> + <td>{l['document_out'] or ''}</td> + </tr> + """) + + po.forecast_html = f""" + <table class="table table-sm table-bordered"> + <thead> + <tr> + <th>Product</th> + <th>Qty</th> + <th>Sale Order</th> + </tr> + </thead> + <tbody> + {''.join(rows)} + </tbody> + </table> + """ + + @api.depends('order_line.product_id', 'order_line.product_qty') + def _compute_forecast_raw(self): + import json + report = self.env['report.stock.report_product_product_replenishment'] + + for po in self: + po.forecast_raw = '[]' + + product_ids = po.order_line.mapped('product_id').ids + if not product_ids: + continue + + data = report._get_report_data(product_variant_ids=product_ids) + + result = [] + for l in data.get('lines', []): + doc = l.get('document_in') + if doc and doc._name == 'purchase.order' and doc.id == po.id: + doc_out = l.get('document_out') + result.append({ + 'product': l['product']['display_name'], + 'quantity': l['quantity'], + 'receipt_date': l.get('receipt_date'), + 'delivery_date': l.get('delivery_date'), + 'document_out': doc_out.display_name if doc_out else '', + 'is_late': l.get('is_late'), + 'replenishment_filled': l.get('replenishment_filled'), + }) + + po.forecast_raw = json.dumps(result) + + def _generate_forecast_lines(self): + report = self.env['report.stock.report_product_product_replenishment'] + Forecast = self.env['purchase.order.forecast.line'] + + for po in self: + + Forecast.search([('order_id','=',po.id)]).unlink() + + product_ids = po.order_line.mapped('product_id').ids + if not product_ids: + continue + + data = report._get_report_data(product_variant_ids=product_ids) + + for l in data.get('lines', []): + doc = l.get('document_in') + + if doc and doc._name == 'purchase.order' and doc.id == po.id: + doc_out = l.get('document_out') + + Forecast.create({ + 'order_id': po.id, + 'product_id': l['product']['id'], + 'quantity': l['quantity'], + 'sale_order': doc_out.display_name if doc_out else '', + 'sale_order_id': doc_out.id if doc_out else '', + 'is_late': bool(l.get('is_late')), + 'replenishment_filled': bool(l.get('replenishment_filled')), + }) def _get_altama_token(self, source='auto'): ICP = self.env['ir.config_parameter'].sudo() @@ -1445,9 +1640,6 @@ class PurchaseOrder(models.Model): self._send_po_not_sync() send_email = True break - - if self.partner_id.id == 5571 and not self.revisi_po: - self.action_create_order_altama() if send_email: if self.is_local_env(): @@ -1484,7 +1676,9 @@ class PurchaseOrder(models.Model): # if len(self) == 1: # _logger.info("Redirecting ke BU") # return self.action_view_related_bu() - + if self.partner_id.id == 5571 and not self.revisi_po: + self.action_create_order_altama() + return res def _remove_product_bom(self): @@ -1689,31 +1883,43 @@ class PurchaseOrder(models.Model): 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 + for line in self.forecast_line_ids: + sale_order_line = self.env['sale.order.line'].search([ + ('product_id', '=', line.product_id.id), + ('order_id', '=', line.sale_order_id.id) + ], limit=1, order='price_reduce_taxexcl') + + purchase_order_line = self.env['purchase.order.line'].search([ + ('product_id', '=', line.product_id.id), + ('order_id', '=', line.order_id.id) + ], limit=1) + + sol_qty = sale_order_line.product_uom_qty + unit_margin = sale_order_line.item_margin / sol_qty if sol_qty else 0 + sum_so_margin += unit_margin * line.quantity + sales_price = sale_order_line.price_reduce_taxexcl * line.quantity 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.ending_price > 0: - if line.taxes_id.id == 22: - ending_price = line.ending_price / 1.11 - purchase_price = ending_price + po_qty = purchase_order_line.product_qty + + if purchase_order_line.ending_price > 0: + if purchase_order_line.taxes_id.id == 22: + ending_price = purchase_order_line.ending_price / 1.11 else: - purchase_price = line.ending_price + ending_price = purchase_order_line.ending_price + + unit_cost = ending_price / po_qty if po_qty else 0 + purchase_price = unit_cost * line.quantity + + else: + unit_cost = purchase_order_line.price_subtotal / po_qty if po_qty else 0 + purchase_price = unit_cost * line.quantity # purchase_price = line.price_subtotal if line.order_id.delivery_amount > 0: - purchase_price += line.delivery_amt_line + purchase_price += purchase_order_line.delivery_amt_line if line.order_id.delivery_amt > 0: purchase_price += line.order_id.delivery_amt real_item_margin = sales_price - purchase_price @@ -1749,23 +1955,25 @@ class PurchaseOrder(models.Model): def compute_total_margin_from_apo(self): sum_so_margin = sum_sales_price = sum_margin = cashback_amount = 0 - for line in self.order_sales_match_line: + for line in self.forecast_line_ids: po_line = self.env['purchase.order.line'].search([ ('product_id', '=', line.product_id.id), - ('order_id', '=', line.purchase_order_id.id) + ('order_id', '=', line.order_id.id) ], limit=1) - sale_order_line = line.sale_line_id or self.env['sale.order.line'].search([ + sale_order_line = self.env['sale.order.line'].search([ ('product_id', '=', line.product_id.id), - ('order_id', '=', line.sale_id.id) + ('order_id', '=', line.sale_order_id.id) ], limit=1, order='price_reduce_taxexcl') if sale_order_line and po_line: - qty_so = line.qty_so or 0 - qty_po = line.qty_po or 0 + qty_so = line.quantity or 0 + qty_po = po_line.product_qty or 0 # Hindari division by zero so_margin = (qty_po / qty_so) * sale_order_line.item_margin if qty_so > 0 else 0 - sum_so_margin += so_margin + sol_qty = sale_order_line.product_uom_qty + unit_margin = sale_order_line.item_margin / sol_qty if sol_qty else 0 + sum_so_margin += unit_margin * line.quantity sales_price = sale_order_line.price_reduce_taxexcl * qty_po if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': @@ -1780,10 +1988,10 @@ class PurchaseOrder(models.Model): purchase_price = po_line.ending_price / 1.11 else: purchase_price = po_line.ending_price - if line.purchase_order_id.delivery_amount > 0: + if line.order_id.delivery_amount > 0: purchase_price += (po_line.delivery_amt_line / po_line.product_qty) * qty_po - if line.purchase_order_id.delivery_amt > 0: - purchase_price += line.purchase_order_id.delivery_amt + if line.order_id.delivery_amt > 0: + purchase_price += line.order_id.delivery_amt if self.partner_id.id == 5571: cashback_percent = line.product_id.x_manufacture.cashback_percent or 0.0 @@ -1992,4 +2200,20 @@ class ChangeDatePlannedWizard(models.TransientModel): }) +class PurchaseOrderForecastLine(models.Model): + _name = 'purchase.order.forecast.line' + _description = 'PO Forecast Coverage' + + order_id = fields.Many2one('purchase.order', ondelete='cascade') + + sale_order_id = fields.Many2one('sale.order', ondelete='cascade') + product_id = fields.Many2one('product.product') + quantity = fields.Float() + + sale_order = fields.Char() + + receipt_date = fields.Datetime() + delivery_date = fields.Datetime() + replenishment_filled = fields.Boolean() + is_late = fields.Boolean() |
