from odoo import models, fields, api, tools, _
from odoo.exceptions import UserError
from datetime import datetime
import math
import logging
_logger = logging.getLogger(__name__)
class RequisitionMatchPO(models.Model):
_name = 'v.requisition.match.po'
_auto = False
_rec_name = 'product_id'
id = fields.Integer(string='ID')
product_id = fields.Many2one('product.product', string='Product')
qty_rpo = fields.Float(string='Qty RPO', help='Qty RPO yang sudah di PO namun SO masih Draft')
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""
create or replace view %s as
select rl.product_id as id, rl.product_id, sum(rl.qty_purchase) as qty_rpo
from requisition_line rl
join requisition r on r.id = rl.requisition_id
join requisition_purchase_match rpm on rpm.requisition_id = r.id
join purchase_order po on po.id = rpm.order_id
join sale_order so on so.id = r.sale_order_id
where 1=1
and r.date_doc >= '2024-11-11'
and po.state in ('done', 'purchase')
and so.state in ('draft', 'sent')
group by rl.product_id
""" % self._table)
class Requisition(models.Model):
_name = 'requisition'
_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', help='isi tanggal hari ini')
description = fields.Char(string='Description', help='bebas isinya apa aja')
requisition_lines = fields.One2many('requisition.line', 'requisition_id', string='Lines', auto_join=True)
notification = fields.Char(string='Notification')
is_po = fields.Boolean(string='Is PO')
requisition_match = fields.One2many('requisition.purchase.match', 'requisition_id', string='Matches', auto_join=True)
sale_order_id = fields.Many2one('sale.order', string='SO', help='harus diisi nomor SO yang ingin digenerate')
sales_approve = fields.Boolean(string='Approval Status', tracking=3, copy=False)
merchandise_approve = fields.Boolean(string='Approval Status', tracking=3, copy=False)
def generate_requisition_from_so(self):
state = ['done', 'sale']
if not self.sale_order_id:
raise UserError('Sale Order Wajib Diisi dan Harus Draft')
if self.sale_order_id.state in state:
raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ')
if not self.sale_order_id.order_line:
raise UserError('Line SO masih kosong, harus diisi dulu')
for order_line in self.sale_order_id.order_line:
param = {
'requisition_id': self.id,
'product_id': order_line.product_id.id,
'partner_id': order_line.vendor_id.id,
'qty_purchase': order_line.product_uom_qty,
'price_unit': order_line.purchase_price,
'taxes_id': order_line.purchase_tax_id.id,
'subtotal': order_line.purchase_price * order_line.product_uom_qty,
'brand_id': order_line.product_id.x_manufacture.id
}
self.env['requisition.line'].create([param])
@api.model
def create(self, vals):
vals['number'] = self.env['ir.sequence'].next_by_code('requisition') or '0'
result = super(Requisition, self).create(vals)
return result
def check_product_line_to_so(self):
for req in self:
for prod_line in req.requisition_lines:
match = self.env['sale.order.line'].search([
('product_id', '=', prod_line.product_id.id), ('order_id', '=', req.sale_order_id.id)
])
if match:
raise UserError ('Product sudah ada di SO tidak bisa approve/create PO')
def button_approve(self):
state = ['done', 'sale']
# self.check_product_line_to_so()
if self.sale_order_id.state in state:
raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ')
if self.env.user.id not in [21, 19, 28]:
raise UserError('Hanya Rafly dan Darren Yang Bisa Approve')
if self.env.user.id == 19 or self.env.user.id == 28:
self.sales_approve = True
elif self.env.user.id == 21 or self.env.user.id == 28:
self.merchandise_approve = True
def create_po_from_requisition(self):
# self.check_product_line_to_so()
if not (self.sales_approve or self.merchandise_approve):
raise UserError('Tidak bisa create PO karena belukm diapprove oleh Darren atau Rafly')
if not self.requisition_lines:
raise UserError('Tidak ada Lines, belum bisa create PO')
if self.is_po:
raise UserError('Sudah pernah di create PO')
if not self.sale_order_id and not (self.sales_approve or self.merchandise_approve):
raise UserError(
'Tidak ada link dengan Sales Order, tidak bisa dihitung sebagai Plafon Qty di PO'
)
vendor_ids = self.env['requisition.line'].read_group([
('requisition_id', '=', self.id),
('partner_id', '!=', False)
], fields=['partner_id'], groupby=['partner_id'])
po_ids = []
for vendor in vendor_ids:
result_po = self.create_po_by_vendor(vendor['partner_id'][0])
po_ids += result_po
return {
'name': _('Purchase Order'),
'view_mode': 'tree,form',
'res_model': 'purchase.order',
'target': 'current',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', po_ids)],
}
def create_po_by_vendor(self, vendor_id):
current_time = datetime.now()
PRODUCT_PER_PO = 20
requisition_line = self.env['requisition.line']
param_header = {
'partner_id': vendor_id,
# 'partner_ref': self.sale_order_id.name,
'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,
'sale_order_id': self.sale_order_id.id,
'note_description': 'from Purchase Requisition'
}
domain = [
('requisition_id', '=', self.id),
('partner_id', '=', vendor_id),
('qty_purchase', '>', 0)
]
products_len = requisition_line.search_count(domain)
page = math.ceil(products_len / PRODUCT_PER_PO)
po_ids = []
# i start from zero (0)
for i in range(page):
new_po = self.env['purchase.order'].create([param_header])
new_po.name = new_po.name + "/R/" + str(i + 1)
po_ids.append(new_po.id)
lines = requisition_line.search(
domain,
offset=i * PRODUCT_PER_PO,
limit=PRODUCT_PER_PO
)
tax = [22]
for line in lines:
product = line.product_id
param_line = {
'order_id' : new_po.id,
'product_id': product.id,
'product_qty': line.qty_purchase,
'product_uom_qty': line.qty_purchase,
'name': product.display_name,
'price_unit': line.price_unit,
'taxes_id': tax,
}
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.env['requisition.purchase.match'].create([{
'requisition_id': self.id,
'order_id': new_po.id
}])
self.is_po = True
return po_ids
# def create_po_from_requisition(self):
# if not self.requisition_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()
# vendor_ids = self.env['requisition.line'].read_group([('requisition_id', '=', self.id), ('partner_id', '!=', False)], fields=['partner_id'], groupby=['partner_id'])
# counter_po_number = 0
# po_ids = []
# for vendor in vendor_ids:
# param_header = {
# 'partner_id': vendor['partner_id'][0],
# # 'partner_ref': self.sale_order_id.name,
# '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,
# 'sale_order_id': self.sale_order_id.id,
# 'note_description': 'from Purchase Requisition'
# }
# param_requisition_line = [
# ('requisition_id', '=', self.id),
# ('partner_id', '=', vendor['partner_id'][0]),
# ('qty_purchase', '>', 0)
# ]
# # new_po = self.env['purchase.order'].create([param_header])
# products_vendors = self.env['requisition.line'].search(, order='brand_id')
# count = brand_id = 0
# for product in products_vendors:
# if count > 200 or brand_id != product.brand_id.id:
# continue
# count = 0
# counter_po_number += 1
# new_po = self.env['purchase.order'].create([param_header])
# new_po.name = new_po.name + "/R/"+str(counter_po_number)
# self.env['requisition.purchase.match'].create([{
# 'requisition_id': self.id,
# 'order_id': new_po.id
# }])
# po_ids.append(new_po.id)
# self.env.cr.commit()
# # else:
# # new_po = self.env['purchase.order'].create([param_header])
# brand_id = product.brand_id.id
# count += 10
# # 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:
# # suggest = 'masih cukup'
# tax = [22]
# param_line = {
# 'sequence': count,
# 'product_id': product.product_id.id,
# 'product_qty': product.qty_purchase,
# 'product_uom_qty': product.qty_purchase,
# 'price_unit': product.price_unit,
# 'taxes_id': tax,
# # 'qty_available_store': qty_available,
# # 'suggest': suggest,
# }
# new_line = self.env['purchase.order.line'].create([param_line])
# if new_po:
# new_line.write({
# 'order_id': new_po.id,
# })
# product.current_po_id = new_po.id
# product.current_po_line_id = new_line.id
# _logger.info('Create PO Line %s' % product.product_id.name)
# # self.notification = self.notification + ' %s' % new_po.name
# self.is_po = True
# if po_ids:
# return {
# 'name': _('Purchase Order'),
# 'view_mode': 'tree,form',
# 'res_model': 'purchase.order',
# 'target': 'current',
# 'type': 'ir.actions.act_window',
# 'domain': [('id', 'in', po_ids)],
# }
class RequisitionLine(models.Model):
_name = 'requisition.line'
_description = 'Requisition Line'
_order = 'requisition_id, id'
_inherit = ['mail.thread']
requisition_id = fields.Many2one('requisition', string='Ref', required=True, ondelete='cascade', index=True, copy=False)
brand_id = fields.Many2one('x_manufactures', string='Brand')
product_id = fields.Many2one('product.product', string='Product', tracking=3,)
partner_id = fields.Many2one('res.partner', string='Vendor')
qty_purchase = fields.Float(string='Qty Purchase')
price_unit = fields.Float(string='Price')
tax_id = fields.Many2one('account.tax', help='isi tax pembelian include atau exclude', domain="[('type_tax_use', '=', 'purchase')]")
subtotal = fields.Float(string='Subtotal')
last_price = fields.Float(string='Last Price')
last_order_id = fields.Many2one('purchase.order', string='Last Order')
last_orderline_id = fields.Many2one('purchase.order.line', string='Last Order Line')
taxes_id = fields.Many2one('account.tax', string='Tax')
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')
source = fields.Char(string='Source', help='data harga diambil darimana')
qty_available_store = fields.Float(string='Available')
suggest = fields.Char(string='Suggest')
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
@api.onchange('price_unit')
def _onchange_price_unit(self):
for line in self:
line.subtotal = line.price_unit * line.qty_purchase
@api.onchange('product_id')
def _onchange_product(self):
for line in self:
line.brand_id = line.product_id.product_tmpl_id.x_manufacture.id
purchase_pricelist = self.env['purchase.pricelist'].search([
('product_id', '=', line.product_id.id)
],order='count_trx_po desc, count_trx_po_vendor desc', limit=1)
price, taxes = line._get_valid_purchase_price(purchase_pricelist)
line.price_unit = price
line.taxes_id = taxes
line.partner_id = purchase_pricelist.vendor_id.id
@api.model
def create(self, vals):
record = super(RequisitionLine, self).create(vals)
record._track_changes('Tambah')
return record
def write(self, vals):
for record in self:
old_values = {field: record[field] for field in vals if field in record}
result = super(RequisitionLine, self).write(vals)
for record in self:
record._track_changes('Updated', old_values)
return result
def unlink(self):
for record in self:
record._track_changes('Hapus')
return super(RequisitionLine, self).unlink()
def _track_changes(self, action, old_values=None):
message = f"Produk telah di-{action} :
"
if action == 'Tambah':
# message += f"
Product: {self.product_id.name}"
message += f"Product: {self.product_id.name}
Vendor: {self.partner_id.name}
Qty: {self.qty_purchase}
Price: {self.price_unit}
Tax: {self.tax_id.name}
Subtotal: {self.subtotal}
Brand: {self.brand_id.x_name}"
elif action == 'Hapus':
# message += f"
Deleted Product: {self.product_id.name}"
message += f"
Deleted Product: {self.product_id.name}
Vendor: {self.partner_id.name} Qty: {self.qty_purchase}
Price: {self.price_unit}
Tax: {self.tax_id.name}
Subtotal: {self.subtotal}
Brand: {self.brand_id.x_name}"
else: # Updated
for field, old_value in old_values.items():
new_value = self[field]
if old_value != new_value:
field_label = self._fields[field].string # Ambil nama label field
message += f"{field_label}: {old_value} -> {new_value}
"
if self.requisition_id:
self.requisition_id.message_post(body=message)
class RequisitionPurchaseMatch(models.Model):
_name = 'requisition.purchase.match'
_order = 'requisition_id, id'
requisition_id = fields.Many2one('requisition', 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