-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BAH-3736, BAH-3751 | Product and Lot price details updation on PO Con…
…firmation and Stock Picking (#165) * BAH-3736 | Add. Extract method to calculate markup price into markup table domain * BAH-3736 | Refactor. Update MRP only on change of product quantity * BAH-3736 | Refactor. Use extracted function for markup price calculation * BAH-3736 | Refactor. Remove unused imports * BAH-3736 | Add. Show MRP field in product supplier info details * BAH-3736 | Add. Configuration to enable updation of product price details * BAH-3736 | Add. Update product price details on purchase order confirmation * BAH-3736 | Refactor. Establish proper inheritance for setting price details for a lot * BAH-3736 | Refactor. Extract batch quantity validation to a separate function in picking validate * BAH-3736 | Add. Update price details for lots on confirmation of a receipt picking * BAH-3736 | Fix. Use product master mrp when seller mrp is not set * BAH-3736 | Fix. Update sale price only when markup is applied * BAH-3736 | Fix. Readonly policies for lot id when existing lot is chosen
- Loading branch information
Showing
11 changed files
with
234 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from . import purchase_order_line | ||
from . import product | ||
from . import price_markup_table | ||
from . import purchase_order | ||
from . import res_config_settings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from odoo import models, fields, api | ||
|
||
|
||
class PurchaseOrder(models.Model): | ||
_inherit = 'purchase.order' | ||
|
||
def button_confirm(self): | ||
res = super(PurchaseOrder, self).button_confirm() | ||
if bool(self.env['ir.config_parameter'].sudo().get_param( | ||
'bahmni_purchase.update_product_prices_on_po_confirm')): | ||
for order in self: | ||
for line in order.order_line: | ||
price_unit, sale_price, mrp = self._calculate_prices(line) | ||
self._update_price_for_supplier(line.product_id.product_tmpl_id.id, price_unit, mrp) | ||
self._update_price_for_product(line.product_id, price_unit, sale_price, mrp) | ||
return res | ||
|
||
def _calculate_prices(self, purchase_order_line): | ||
price_unit = purchase_order_line.price_unit | ||
mrp = purchase_order_line.mrp | ||
tax = purchase_order_line.price_tax / purchase_order_line.product_qty if purchase_order_line.product_qty > 0 else purchase_order_line.price_tax | ||
# Compute the price_unit,mrp for the template's UoM, because the supplier's UoM is related to that UoM. | ||
if purchase_order_line.product_id.product_tmpl_id.uom_po_id != purchase_order_line.product_uom: | ||
default_uom = purchase_order_line.product_id.product_tmpl_id.uom_po_id | ||
price_unit = purchase_order_line.product_uom._compute_price(price_unit, default_uom) | ||
mrp = purchase_order_line.product_uom._compute_price(mrp, default_uom) | ||
tax = purchase_order_line.product_uom._compute_price(tax, default_uom) | ||
total_cost = price_unit + tax | ||
sale_price = self.env['price.markup.table'].calculate_price_with_markup(total_cost) | ||
# Set sale_price as none if no markup value is added | ||
if sale_price == total_cost: | ||
sale_price = None | ||
return price_unit, sale_price, mrp | ||
|
||
def _update_price_for_product(self, product_id, price_unit, sale_price, mrp): | ||
if sale_price: | ||
product_id.write({'standard_price': price_unit, 'list_price': sale_price, 'mrp': mrp}) | ||
else: | ||
product_id.write({'standard_price': price_unit, 'mrp': mrp}) | ||
|
||
def _update_price_for_supplier(self, product_tmpl_id, price_unit, mrp): | ||
seller_info = self.env['product.supplierinfo'].search([('partner_id', '=', self.partner_id.id), | ||
('product_tmpl_id', '=', product_tmpl_id)]) | ||
seller_info.mrp = mrp | ||
seller_info.price = price_unit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from odoo import models, fields, api | ||
|
||
|
||
class ResConfigSettings(models.TransientModel): | ||
_inherit = "res.config.settings" | ||
|
||
update_product_prices_on_po_confirmation = fields.Boolean( | ||
string="Update price details of the product on Purchase Order Confirmation", | ||
config_parameter="bahmni_purchase.update_product_prices_on_po_confirm") | ||
|
||
def set_values(self): | ||
res = super(ResConfigSettings, self).set_values() | ||
self.env['ir.config_parameter'].sudo().set_param('bahmni_purchase.update_product_prices_on_po_confirm', | ||
self.update_product_prices_on_po_confirmation) | ||
return res | ||
|
||
@api.model | ||
def get_values(self): | ||
res = super(ResConfigSettings, self).get_values() | ||
ICPSudo = self.env['ir.config_parameter'].sudo() | ||
res.update( | ||
update_product_prices_on_po_confirmation=ICPSudo.get_param('bahmni_purchase.update_product_prices_on_po_confirm'), | ||
) | ||
return res |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<odoo> | ||
<record id="res_config_settings_view_bahmni_payments" model="ir.ui.view"> | ||
<field name="name">res.config.settings.view.form.inherit.bahmni.payments</field> | ||
<field name="model">res.config.settings</field> | ||
<field name="priority" eval="1"/> | ||
<field name="inherit_id" ref="bahmni_sale.res_config_settings_view_form_i"/> | ||
<field name="arch" type="xml"> | ||
<xpath expr="//div[@name='bahmni-custom-configs']" position="inside"> | ||
<div class="row mt16 o_settings_container" name="bahmni_purchase_configurations"> | ||
<h2 style="text-align: center;">Purchase Configurations</h2> | ||
<div class="col-12 col-lg-6 o_setting_box" id="variant_options"> | ||
<div class="o_setting_left_pane"> | ||
<field name="update_product_prices_on_po_confirmation"/> | ||
</div> | ||
<div class="o_setting_right_pane"> | ||
<label for="update_product_prices_on_po_confirmation"/> | ||
<div class="text-muted"> | ||
Enabling this config will update the Cost, Sale Price, MRP of the product from the PO on confirmation | ||
</div> | ||
</div> | ||
|
||
</div> | ||
</div> | ||
|
||
</xpath> | ||
</field> | ||
</record> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,109 +1,73 @@ | ||
|
||
import datetime | ||
from dateutil import tz | ||
from collections import Counter, defaultdict | ||
|
||
from collections import defaultdict | ||
from odoo import models, fields, api | ||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DTF | ||
|
||
from odoo.exceptions import UserError, ValidationError, AccessError, RedirectWarning | ||
|
||
|
||
class StockMoveLine(models.Model): | ||
_inherit = 'stock.move.line' | ||
_inherit = 'stock.move.line' | ||
|
||
sale_price = fields.Float(string="Sale Price") | ||
mrp = fields.Float(string="MRP") | ||
cost_price = fields.Float(string="Cost Price") | ||
balance = fields.Float(string="Balance") | ||
cost_price = fields.Float(string="Cost Price") | ||
balance = fields.Float(string="Balance") | ||
existing_lot_id = fields.Many2one( | ||
'stock.lot', 'Lot/Serial Number', | ||
domain="[('product_id', '=', product_id), ('company_id', '=', company_id)]", check_company=True) | ||
|
||
@api.onchange('product_id','qty_done') | ||
def _onchange_balance_qty(self): | ||
def _onchange_balance_qty(self): | ||
if self.location_id and self.product_id: | ||
self.balance = (sum([stock.inventory_quantity_auto_apply for stock in self.env['stock.quant'].search([('location_id', '=', self.location_id.id),('product_id', '=', self.product_id.id)])])) - self.qty_done | ||
|
||
@api.onchange('existing_lot_id') | ||
def _onchange_existing_lot_id(self): | ||
if self.existing_lot_id: | ||
self.lot_name = self.existing_lot_id.name | ||
self.expiration_date = self.existing_lot_id.expiration_date | ||
def _onchange_existing_lot_id(self): | ||
if self.existing_lot_id: | ||
self.lot_name = self.existing_lot_id.name | ||
self.expiration_date = self.existing_lot_id.expiration_date | ||
else: | ||
self.lot_name = '' | ||
self.lot_name = '' | ||
self.expiration_date = fields.Datetime.today() + datetime.timedelta(days=self.product_id.expiration_time) | ||
|
||
@api.constrains('mrp') | ||
def _check_fields_values(self): | ||
for rec in self: | ||
if rec.mrp > 0.00: | ||
if rec.mrp > 0.00: | ||
if rec.sale_price > rec.mrp: | ||
raise ValidationError('Sales Price should not be greater than MRP Rate.') | ||
else: | ||
pass | ||
|
||
@api.onchange('cost_price') | ||
def _check_cost_price_values(self): | ||
if self.cost_price > 0.00: | ||
markup_table_line = self.env['price.markup.table'].search([('lower_price', '<', self.cost_price), | ||
'|', ('higher_price', '>=', self.cost_price), | ||
('higher_price', '=', 0)],limit=1) | ||
if markup_table_line: | ||
self.sale_price = self.cost_price + (self.cost_price * markup_table_line.markup_percentage / 100) | ||
else: | ||
self.sale_price = self.cost_price | ||
else: | ||
pass | ||
|
||
def _check_cost_price_values(self): | ||
if self.cost_price > 0.00: | ||
self.sale_price = self.env['price.markup.table'].calculate_price_with_markup(self.cost_price) | ||
|
||
@api.model | ||
def default_get(self, fields): | ||
res = super(StockMoveLine, self).default_get(fields) | ||
cost_value = 0.00 | ||
move_ids = self.env['stock.move'].search([('picking_id', '=', res.get('picking_id')),('product_id', '=', res.get('product_id'))],limit=1) | ||
if move_ids: | ||
cost_value = move_ids.purchase_line_id.price_unit + (move_ids.purchase_line_id.price_tax / move_ids.purchase_line_id.product_qty) | ||
cost_value_per_unit = float(cost_value) * float(move_ids.purchase_line_id.product_uom.factor) | ||
if cost_value_per_unit > 0.00: | ||
markup_table_line = self.env['price.markup.table'].search([('lower_price', '<', cost_value_per_unit), | ||
'|', ('higher_price', '>=', cost_value_per_unit), | ||
('higher_price', '=', 0)],limit=1) | ||
if markup_table_line: | ||
res.update({'cost_price': cost_value_per_unit,'sale_price': cost_value_per_unit + (cost_value_per_unit * markup_table_line.markup_percentage / 100)}) | ||
else: | ||
res.update({'cost_price': cost_value_per_unit,'sale_price': cost_value_per_unit }) | ||
associated_purchase_line = move_ids.purchase_line_id | ||
if associated_purchase_line: | ||
res.update({'mrp': associated_purchase_line.product_uom._compute_price(associated_purchase_line.mrp, self.product_uom_id)}) | ||
total_cost_value = associated_purchase_line.price_unit + (associated_purchase_line.price_tax / associated_purchase_line.product_qty) | ||
cost_value_per_unit = associated_purchase_line.product_uom._compute_price(total_cost_value, self.product_uom_id) | ||
if cost_value_per_unit > 0.00: | ||
res.update({'cost_price': cost_value_per_unit, | ||
'sale_price': self.env['price.markup.table'].calculate_price_with_markup(cost_value_per_unit) | ||
}) | ||
else: | ||
pass | ||
return res | ||
|
||
def _create_and_assign_production_lot(self): | ||
""" Creates and assign new production lots for move lines.""" | ||
lot_vals = [] | ||
# It is possible to have multiple time the same lot to create & assign, | ||
# so we handle the case with 2 dictionaries. | ||
key_to_index = {} # key to index of the lot | ||
key_to_mls = defaultdict(lambda: self.env['stock.move.line']) # key to all mls | ||
for ml in self: | ||
ml.product_id.write({'standard_price': ml.cost_price,'list_price': ml.sale_price,'mrp': ml.mrp}) | ||
key = (ml.company_id.id, ml.product_id.id, ml.lot_name, ml.cost_price, ml.sale_price, ml.mrp, ml.expiration_date) | ||
key_to_mls[key] |= ml | ||
if ml.tracking != 'lot' or key not in key_to_index: | ||
key_to_index[key] = len(lot_vals) | ||
lot_vals.append(ml._get_value_production_lot()) | ||
lots = self.env['stock.lot'].create(lot_vals) | ||
for key, mls in key_to_mls.items(): | ||
lot = lots[key_to_index[key]].with_prefetch(lots._ids) # With prefetch to reconstruct the ones broke by accessing by index | ||
mls.write({'lot_id': lot.id}) | ||
|
||
|
||
# This function is overridden to set price details for the lot to help markup feature. | ||
# This will be called on receive products | ||
def _get_value_production_lot(self): | ||
self.ensure_one() | ||
|
||
return { | ||
'company_id': self.company_id.id, | ||
'name': self.lot_name, | ||
'product_id': self.product_id.id, | ||
res = super(StockMoveLine,self)._get_value_production_lot() | ||
res.update({ | ||
'cost_price': self.cost_price, | ||
'sale_price': self.sale_price, | ||
'mrp': self.mrp, | ||
'expiration_date': self.expiration_date | ||
} | ||
|
||
'mrp': self.mrp | ||
}) | ||
return res |
Oops, something went wrong.