245 lines
9.1 KiB
Python
245 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
from email.policy import default
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import ValidationError, UserError
|
|
|
|
|
|
class Grn(models.Model):
|
|
_name = 'grn'
|
|
_description = 'Goods Receipt Note'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin', 'product.catalog.mixin']
|
|
|
|
name = fields.Char(
|
|
string='Name',
|
|
copy=False,
|
|
readonly=True,
|
|
default=lambda self: _('New')
|
|
)
|
|
vendor_id = fields.Many2one('res.partner', string='Vendor', readonly=True, )
|
|
note = fields.Text(string='Source Document / Note')
|
|
date = fields.Date(string='Date', default=fields.Date.context_today, copy=False)
|
|
picking_id = fields.Many2one("stock.picking", string="Stock Transfer", copy=False)
|
|
state = fields.Selection(
|
|
[('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')],
|
|
string='State',
|
|
default='draft',
|
|
tracking=True
|
|
)
|
|
grn_line_ids = fields.One2many('grn.lines', 'grn_id', string="GRN Lines")
|
|
received_by = fields.Many2one('res.users', string='Received By')
|
|
location_id = fields.Many2one(
|
|
'stock.location',
|
|
string='To',
|
|
domain="[('usage', '=', 'internal')]",
|
|
required=True
|
|
)
|
|
total_amount = fields.Float(string='Amount', compute='_compute_total_amount')
|
|
company_id = fields.Many2one(
|
|
'res.company',
|
|
string='Company',
|
|
required=True,
|
|
readonly=True,
|
|
default=lambda self: self.env.company,
|
|
)
|
|
|
|
def _get_product_catalog_order_data(self, products, **kwargs):
|
|
res = super()._get_product_catalog_order_data(products, **kwargs)
|
|
for product in products:
|
|
res[product.id] |= {
|
|
'price': product.standard_price,
|
|
}
|
|
return res
|
|
|
|
def _get_product_catalog_domain(self):
|
|
"""Get the domain to search for products in the catalog.
|
|
"""
|
|
domain = [('company_id', 'in', [self.company_id.id, False]), ('type', '=', 'consu'), ('purchase_ok', '=', True)]
|
|
return domain
|
|
|
|
def _update_order_line_info(self, product_id, quantity, **kwargs):
|
|
self.ensure_one()
|
|
pol = self.grn_line_ids.filtered(lambda line: line.product_id.id == product_id)
|
|
if pol:
|
|
if quantity != 0:
|
|
pol.quantity = quantity
|
|
else:
|
|
pol.unlink()
|
|
elif quantity > 0:
|
|
pol = self.env['grn.lines'].create({
|
|
'grn_id': self.id,
|
|
'product_id': product_id,
|
|
'quantity': quantity,
|
|
})
|
|
else:
|
|
pass
|
|
return 0
|
|
|
|
@api.depends('grn_line_ids')
|
|
def _compute_total_amount(self):
|
|
"""Compute the value of the field computed_field."""
|
|
for record in self:
|
|
amount = 0
|
|
for line in record.grn_line_ids:
|
|
amount += (line.quantity * line.price)
|
|
record.total_amount = amount
|
|
|
|
def _get_product_catalog_lines_data(self):
|
|
catalog_info = {
|
|
'quantity': self.quantity,
|
|
'price': self.product_id.standard_price,
|
|
}
|
|
return catalog_info
|
|
|
|
def button_action_confirm(self):
|
|
"""Confirm the GRN and assign sequence and received user"""
|
|
if not self.grn_line_ids:
|
|
raise UserError(_(
|
|
"Cannot confirm the GRN '%s' because no products have been added in the lines. "
|
|
"Please add at least one product before confirming."
|
|
) % self.name)
|
|
|
|
if not self.name or self.name == _('New'):
|
|
self.name = self.env['ir.sequence'].next_by_code('grn') or _('New')
|
|
self.state = 'confirmed'
|
|
self.received_by = self.env.user
|
|
for line in self.grn_line_ids:
|
|
if line.price:
|
|
continue
|
|
else:
|
|
line.price = line.product_id.standard_price
|
|
|
|
def button_action_done(self):
|
|
"""Set GRN state to done if picking is completed"""
|
|
if not self.picking_id or self.picking_id.state != 'done':
|
|
raise ValidationError(_(
|
|
"Cannot mark the GRN '%s' as done because the associated stock transfer is not yet completed. "
|
|
"Please complete the transfer before proceeding."
|
|
) % (self.name,))
|
|
if self.state == 'confirmed':
|
|
self.state = 'done'
|
|
|
|
def button_action_cancel(self):
|
|
"""Cancel GRN if stock transfer is not done"""
|
|
if self.picking_id and self.picking_id.state == 'done':
|
|
raise ValidationError(_(
|
|
"Cannot cancel the GRN '%s' because the stock transfer '%s' has already been completed. "
|
|
) % (self.name, self.picking_id.name))
|
|
if self.picking_id:
|
|
self.picking_id.action_cancel()
|
|
self.picking_id.unlink()
|
|
self.state = 'cancel'
|
|
|
|
def button_action_reset_to_draft(self):
|
|
self.state = 'draft'
|
|
|
|
def button_create_transfer(self):
|
|
"""Create stock picking for confirmed GRN"""
|
|
if self.picking_id:
|
|
return
|
|
if self.state != 'confirmed':
|
|
raise UserError(_(
|
|
"The GRN '%s' cannot be transferred because it is not confirmed yet. "
|
|
"Please confirm the GRN first."
|
|
) % self.name)
|
|
|
|
picking_type = self.env['stock.picking.type'].search([('code', '=', 'incoming')], limit=1)
|
|
if not picking_type:
|
|
raise UserError(_(
|
|
"No Incoming Picking Type is configured in the system. "
|
|
"Please create an Incoming Picking Type before transferring GRN '%s'."
|
|
) % self.name)
|
|
|
|
picking = self.env['stock.picking'].create({
|
|
'origin': self.name,
|
|
'location_dest_id': self.location_id.id,
|
|
'location_id': picking_type.default_location_src_id.id,
|
|
'picking_type_id': picking_type.id,
|
|
'partner_id': self.vendor_id.id
|
|
})
|
|
|
|
moves = []
|
|
for line in self.grn_line_ids:
|
|
moves.append((0, 0, {
|
|
'name': line.product_id.name,
|
|
'product_id': line.product_id.id,
|
|
'product_uom': line.product_uom_id.id,
|
|
'product_uom_qty': line.quantity,
|
|
'location_id': picking_type.default_location_src_id.id,
|
|
'location_dest_id': self.location_id.id,
|
|
'price_unit': line.price if line.price > 1 else line.product_id.standard_price,
|
|
}))
|
|
picking.write({'move_ids_without_package': moves})
|
|
picking.action_confirm()
|
|
for move in picking:
|
|
move.location_dest_id = self.location_id
|
|
picking.location_dest_id = self.location_id
|
|
self.picking_id = picking
|
|
|
|
def action_view_transfer(self):
|
|
"""Return action to view related stock picking"""
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('Receipts'),
|
|
'res_model': 'stock.picking',
|
|
'view_mode': 'form',
|
|
'target': 'current',
|
|
'res_id': self.picking_id.id,
|
|
}
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
return super().create(vals_list)
|
|
|
|
def write(self, vals):
|
|
return super().write(vals)
|
|
|
|
def unlink(self):
|
|
if any(rec.state not in ['cancel', 'draft'] for rec in self):
|
|
raise UserError(_(
|
|
"Unable to delete the GRN '%s' because it has already been confirmed or processed. "
|
|
) % self.name)
|
|
return super().unlink()
|
|
|
|
|
|
class GrnLines(models.Model):
|
|
_name = 'grn.lines'
|
|
_description = 'GRN Lines'
|
|
|
|
grn_id = fields.Many2one('grn', string="GRN", required=True, ondelete='cascade')
|
|
product_id = fields.Many2one(
|
|
'product.product',
|
|
string='Product',
|
|
ondelete="cascade",
|
|
domain=[('type', '!=', 'service'), ('purchase_ok', '=', True)],
|
|
index=True
|
|
)
|
|
product_uom_id = fields.Many2one(related='product_id.uom_id', string='Unit of Measure')
|
|
quantity = fields.Float('Quantity', digits='Product Unit of Measure')
|
|
price = fields.Float("Unit Price")
|
|
total_price = fields.Float("Total Price", compute='_compute_product_total_price', store=True)
|
|
|
|
@api.depends('quantity', 'price')
|
|
def _compute_product_total_price(self):
|
|
for rec in self:
|
|
rec.total_price = rec.quantity * rec.price if rec.quantity and rec.price else 0
|
|
|
|
@api.constrains('product_id')
|
|
def _check_unique_product_grn(self):
|
|
for rec in self:
|
|
duplicate_lines = rec.grn_id.grn_line_ids.filtered(lambda l: l.product_id == rec.product_id)
|
|
if len(duplicate_lines) > 1:
|
|
raise UserError(_('The product %s already exists in the GRN.') % rec.product_id.display_name)
|
|
|
|
def action_add_from_catalog(self):
|
|
order = self.env['grn'].browse(self.env.context.get('order_id'))
|
|
return order.with_context(child_field='grn_line_ids').action_add_from_catalog()
|
|
|
|
def unlink(self):
|
|
if any(rec.grn_id.state not in ['cancel', 'draft'] for rec in self):
|
|
raise UserError(_(
|
|
"Unable to delete the GRN '%s' because it has already been confirmed or processed. "
|
|
) % self.name)
|
|
return super().unlink()
|