mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Merge commit '9428370987aede5e756cc8c38d796af3296e07a4' into 11.0
This commit is contained in:
@@ -109,36 +109,17 @@ class ProviderStamps(models.Model):
|
||||
|
||||
ret_val = service.create_shipping()
|
||||
ret_val.ShipDate = date_planned.split()[0] if date_planned else date.today().isoformat()
|
||||
ret_val.FromZIPCode = order.warehouse_id.partner_id.zip
|
||||
ret_val.FromZIPCode = self.get_shipper_warehouse(order=order).zip
|
||||
ret_val.ToZIPCode = order.partner_shipping_id.zip
|
||||
ret_val.PackageType = self._stamps_package_type()
|
||||
ret_val.ServiceType = self.stamps_service_type
|
||||
ret_val.WeightLb = weight
|
||||
return ret_val
|
||||
|
||||
def _get_order_for_picking(self, picking):
|
||||
if picking.sale_id:
|
||||
return picking.sale_id
|
||||
return None
|
||||
|
||||
def _get_company_for_order(self, order):
|
||||
company = order.company_id
|
||||
if order.team_id and order.team_id.subcompany_id:
|
||||
company = order.team_id.subcompany_id.company_id
|
||||
elif order.analytic_account_id and order.analytic_account_id.subcompany_id:
|
||||
company = order.analytic_account_id.subcompany_id.company_id
|
||||
return company
|
||||
|
||||
def _get_company_for_picking(self, picking):
|
||||
order = self._get_order_for_picking(picking)
|
||||
if order:
|
||||
return self._get_company_for_order(order)
|
||||
return picking.company_id
|
||||
|
||||
def _stamps_get_addresses_for_picking(self, picking):
|
||||
company = self._get_company_for_picking(picking)
|
||||
from_ = picking.picking_type_id.warehouse_id.partner_id
|
||||
to = picking.partner_id
|
||||
company = self.get_shipper_company(picking=picking)
|
||||
from_ = self.get_shipper_warehouse(picking=picking)
|
||||
to = self.get_recipient(picking=picking)
|
||||
return company, from_, to
|
||||
|
||||
def _stamps_get_shippings_for_picking(self, service, picking):
|
||||
@@ -251,7 +232,7 @@ class ProviderStamps(models.Model):
|
||||
company, from_partner, to_partner = self._stamps_get_addresses_for_picking(picking)
|
||||
|
||||
from_address = service.create_address()
|
||||
from_address.FullName = company.partner_id.name
|
||||
from_address.FullName = company.name
|
||||
from_address.Address1 = from_partner.street
|
||||
if from_partner.street2:
|
||||
from_address.Address2 = from_partner.street2
|
||||
|
||||
1
hr_holidays_partial/__init__.py
Normal file
1
hr_holidays_partial/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
18
hr_holidays_partial/__manifest__.py
Normal file
18
hr_holidays_partial/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
'name': 'HR Holidays Partial',
|
||||
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||
'version': '11.0.0.0.0',
|
||||
'category': 'Human Resources',
|
||||
'sequence': 95,
|
||||
'summary': 'Partial day leave requests displau hours',
|
||||
'description': """
|
||||
Create and display leave requests in hours for partial days.
|
||||
""",
|
||||
'website': 'https://hibou.io/',
|
||||
'depends': ['hr_holidays'],
|
||||
'data': [
|
||||
'views/hr_holidays_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
}
|
||||
1
hr_holidays_partial/models/__init__.py
Normal file
1
hr_holidays_partial/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import hr_holiday
|
||||
30
hr_holidays_partial/models/hr_holiday.py
Normal file
30
hr_holidays_partial/models/hr_holiday.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from odoo import api, fields, models
|
||||
from math import ceil
|
||||
from odoo.addons.hr_holidays.models.hr_holidays import HOURS_PER_DAY
|
||||
|
||||
|
||||
class HRHoliday(models.Model):
|
||||
_inherit = 'hr.holidays'
|
||||
|
||||
days_in_hours = fields.Float(string="Hours", compute='_get_days_in_hours')
|
||||
|
||||
def _get_number_of_days(self, date_from, date_to, employee_id):
|
||||
from_dt = fields.Datetime.from_string(date_from)
|
||||
to_dt = fields.Datetime.from_string(date_to)
|
||||
|
||||
if employee_id:
|
||||
employee = self.env['hr.employee'].browse(employee_id)
|
||||
num_days = employee.get_work_days_count(from_dt, to_dt)
|
||||
if num_days == 0:
|
||||
time_delta = to_dt - from_dt
|
||||
hours = (time_delta.seconds / 3600)
|
||||
return hours / HOURS_PER_DAY
|
||||
else:
|
||||
return employee.get_work_days_count(from_dt, to_dt)
|
||||
|
||||
time_delta = to_dt - from_dt
|
||||
return ceil(time_delta.days + float(time_delta.seconds) / 86400)
|
||||
|
||||
@api.depends('number_of_days_temp')
|
||||
def _get_days_in_hours(self):
|
||||
self.days_in_hours = self.number_of_days_temp * 8
|
||||
14
hr_holidays_partial/views/hr_holidays_views.xml
Normal file
14
hr_holidays_partial/views/hr_holidays_views.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="hr_holidays_edit_holiday_new_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.holidays.edit.holiday.new.inherit</field>
|
||||
<field name="model">hr.holidays</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="hr_holidays.edit_holiday_new"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group/group[1]" position="inside">
|
||||
<field name="days_in_hours" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -19,10 +19,10 @@ ytd += contract.external_wages
|
||||
remaining = 7000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
@@ -63,10 +63,10 @@ ytd += contract.external_wages
|
||||
remaining = 7000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
@@ -107,10 +107,10 @@ ytd += contract.external_wages
|
||||
remaining = 114967.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 7000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -19,10 +19,10 @@ ytd += contract.external_wages
|
||||
remaining = 128400.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
@@ -34,7 +34,7 @@ else:
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (payslip.date_to[:4] == '2018') and not contract.fica_exempt</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = categories.GROSS</field>
|
||||
<field name="amount_python_compute">result = categories.BASIC</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_fica_emp_m_add_wages_2018" model="hr.salary.rule">
|
||||
@@ -577,10 +577,10 @@ ytd += contract.external_wages
|
||||
remaining = 7000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 14000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 12500.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -45,7 +45,7 @@ class TestUsMoPayslip(TestUsPayslip):
|
||||
# tax rates
|
||||
mo_unemp = contract.mo_unemp_rate(2018) / -100.0
|
||||
|
||||
self._log('2018 Missouri tax first payslip:')
|
||||
self._log('2018 Missouri tax single first payslip:')
|
||||
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||
|
||||
payslip.compute_sheet()
|
||||
@@ -62,11 +62,12 @@ class TestUsMoPayslip(TestUsPayslip):
|
||||
|
||||
# 5000 for single and married with spouse working, 10000 for married spouse not working
|
||||
us_withholding = min(5000, us_withholding)
|
||||
|
||||
# 5000
|
||||
self._log(us_withholding)
|
||||
|
||||
mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated - us_withholding
|
||||
# 48306.14
|
||||
self._log(mo_taxable_income)
|
||||
# 44000.0
|
||||
self._log('%s = %s - %s - %s - %s' % (mo_taxable_income, gross_salary, standard_deduction, mo_allowance_calculated, us_withholding))
|
||||
|
||||
remaining_taxable_income = mo_taxable_income
|
||||
tax = 0.0
|
||||
|
||||
@@ -17,10 +17,10 @@ ytd += contract.external_wages
|
||||
remaining = 7000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -19,10 +19,10 @@ ytd += contract.external_wages
|
||||
remaining = 33700.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
@@ -82,10 +82,10 @@ ytd += contract.external_wages
|
||||
remaining = 33700.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
@@ -144,10 +144,10 @@ ytd += contract.external_wages
|
||||
remaining = 33700.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
@@ -183,10 +183,10 @@ ytd += contract.external_wages
|
||||
remaining = 33700.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -19,10 +19,10 @@ ytd += contract.external_wages
|
||||
remaining = 11100.00 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 9500.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 10000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -14,7 +14,7 @@ class TestUsPAPayslip(TestUsPayslip):
|
||||
wh = -127.92
|
||||
|
||||
employee = self._createEmployee()
|
||||
employee.company_id.pa_unemp_employee_rate_2018 = 0.06~
|
||||
employee.company_id.pa_unemp_employee_rate_2018 = 0.06
|
||||
employee.company_id.pa_unemp_company_rate_2018 = 3.6785
|
||||
employee.company_id.pa_withhold_rate_2018 = 3.07
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 9000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 8000.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
@@ -18,10 +18,10 @@ ytd += contract.external_wages
|
||||
remaining = 47300.0 - ytd
|
||||
if remaining <= 0.0:
|
||||
result = 0
|
||||
elif remaining < categories.GROSS:
|
||||
elif remaining < categories.BASIC:
|
||||
result = remaining
|
||||
else:
|
||||
result = categories.GROSS
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
1
pos_product_catch_weight/__init__.py
Executable file
1
pos_product_catch_weight/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
28
pos_product_catch_weight/__manifest__.py
Executable file
28
pos_product_catch_weight/__manifest__.py
Executable file
@@ -0,0 +1,28 @@
|
||||
# Copyright 2018 Tecnativa S.L. - David Vidal
|
||||
# ^^^ For OCA `pos_lot_selection`
|
||||
# Copyright 2018 Hibou Corp. - Jared Kipe
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{
|
||||
'name': 'POS Catch Weight',
|
||||
'version': '11.0.1.0.0',
|
||||
'category': 'Point of Sale',
|
||||
'author': 'Hibou Corp., '
|
||||
'Tecnativa,'
|
||||
'Odoo Community Association (OCA)',
|
||||
'website': 'https://github.com/OCA/pos',
|
||||
'license': 'AGPL-3',
|
||||
'depends': [
|
||||
'point_of_sale',
|
||||
'product_catch_weight',
|
||||
],
|
||||
'data': [
|
||||
'templates/assets.xml',
|
||||
'views/pos_views.xml',
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/pos.xml'
|
||||
],
|
||||
'application': False,
|
||||
'installable': True,
|
||||
}
|
||||
1
pos_product_catch_weight/models/__init__.py
Normal file
1
pos_product_catch_weight/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import pos_order
|
||||
57
pos_product_catch_weight/models/pos_order.py
Normal file
57
pos_product_catch_weight/models/pos_order.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class PosOrderLine(models.Model):
|
||||
_inherit = 'pos.order.line'
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
quant_model = self.sudo().env['stock.quant']
|
||||
res = super(PosOrderLine, self).create(values)
|
||||
if res.pack_lot_ids:
|
||||
lot_names = res.pack_lot_ids.mapped('lot_name')
|
||||
product_id = res.product_id.id
|
||||
quants = quant_model.search([
|
||||
('product_id', '=', product_id),
|
||||
('location_id', '=', res.order_id.location_id.id),
|
||||
('lot_id', '!=', False),
|
||||
('lot_id.name', 'in', lot_names),
|
||||
])
|
||||
for l in res.pack_lot_ids:
|
||||
named_quants = quants.filtered(lambda q: q.lot_id.name == l.lot_name)
|
||||
for q in named_quants:
|
||||
l.lot_id = q.lot_id
|
||||
return res
|
||||
|
||||
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id', 'pack_lot_ids.lot_id')
|
||||
def _compute_amount_line_all(self):
|
||||
# This is a hard override due to the closed nature of this method.
|
||||
for line in self:
|
||||
fpos = line.order_id.fiscal_position_id
|
||||
tax_ids_after_fiscal_position = fpos.map_tax(line.tax_ids, line.product_id,
|
||||
line.order_id.partner_id) if fpos else line.tax_ids
|
||||
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
||||
|
||||
lot_ratio_sum = 0.0
|
||||
for l in line.pack_lot_ids:
|
||||
if l.lot_id:
|
||||
lot_ratio_sum += l.lot_catch_weight_ratio
|
||||
else:
|
||||
lot_ratio_sum += 1.0
|
||||
if lot_ratio_sum != 0.0:
|
||||
lot_ratio = lot_ratio_sum / line.qty
|
||||
price = (line.price_unit * lot_ratio) * (1 - (line.discount or 0.0) / 100.0)
|
||||
|
||||
taxes = tax_ids_after_fiscal_position.compute_all(price, line.order_id.pricelist_id.currency_id, line.qty,
|
||||
product=line.product_id, partner=line.order_id.partner_id)
|
||||
line.update({
|
||||
'price_subtotal_incl': taxes['total_included'],
|
||||
'price_subtotal': taxes['total_excluded'],
|
||||
})
|
||||
|
||||
|
||||
class PosOrderLineLot(models.Model):
|
||||
_inherit = 'pos.pack.operation.lot'
|
||||
|
||||
lot_id = fields.Many2one('stock.production.lot', string='Lot')
|
||||
lot_catch_weight_ratio = fields.Float(related='lot_id.catch_weight_ratio')
|
||||
30
pos_product_catch_weight/static/src/css/pos.css
Executable file
30
pos_product_catch_weight/static/src/css/pos.css
Executable file
@@ -0,0 +1,30 @@
|
||||
/* Copyright 2018 Tecnativa - David Vidal
|
||||
Copyright 2018 Hibou Corp. - Jared Kipe
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
|
||||
*/
|
||||
|
||||
.pos .popup .packlot-select {
|
||||
border-bottom: solid 1px rgba(60,60,60,0.1);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.pos .popup select {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
border: solid 1px #cecbcb;
|
||||
margin-bottom: 4px;
|
||||
background: white;
|
||||
font-family: "Lato","Lucida Grande", Helvetica, Verdana, Arial;
|
||||
color: #444;
|
||||
width: 80%;
|
||||
min-height: 44px;
|
||||
font-size: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pos .popup select:focus, .pos .popup select:active {
|
||||
outline: none;
|
||||
box-shadow: 0px 0px 0px 3px #6EC89B;
|
||||
}
|
||||
74
pos_product_catch_weight/static/src/js/chrome.js
Executable file
74
pos_product_catch_weight/static/src/js/chrome.js
Executable file
@@ -0,0 +1,74 @@
|
||||
/* Copyright 2018 Tecnativa - David Vidal
|
||||
Copyright 2018 Hibou Corp. - Jared Kipe
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
odoo.define("pos_product_catch_weight.chrome", function (require) {
|
||||
"use strict";
|
||||
|
||||
var chrome = require("point_of_sale.chrome");
|
||||
|
||||
chrome.Chrome.include({
|
||||
build_widgets: function () {
|
||||
var res = this._super.apply(this, arguments);
|
||||
var packlotline = this.gui.popup_instances.packlotline;
|
||||
// Add events over instanced popup
|
||||
var events = {
|
||||
"change .packlot-line-select": "lot_to_input",
|
||||
};
|
||||
packlotline.events = Object.assign(
|
||||
packlotline.events, events
|
||||
);
|
||||
// Add methods over instanced popup
|
||||
// Write the value in the corresponding input
|
||||
packlotline.lot_to_input = function (event) {
|
||||
var $select = $(event.target);
|
||||
var $option = this.$("select.packlot-line-select option");
|
||||
var $input = this.$el.find("input");
|
||||
if ($input.length) {
|
||||
for (var i = 0; i < $input.length; i++) {
|
||||
var $i = $input[i];
|
||||
if (!$i.value || i + 1 == $input.length) {
|
||||
$i.value = $select[0].value;
|
||||
$i.blur();
|
||||
$i.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
$option.prop('selected', function () {
|
||||
return this.defaultSelected;
|
||||
});
|
||||
};
|
||||
|
||||
packlotline.click_confirm = function(){
|
||||
var pack_lot_lines = this.options.pack_lot_lines;
|
||||
this.$('.packlot-line-input').each(function(index, el){
|
||||
var cid = $(el).attr('cid'),
|
||||
lot_name = $(el).val();
|
||||
var pack_line = pack_lot_lines.get({cid: cid});
|
||||
var quant = null;
|
||||
for (var i = 0; i < pack_lot_lines.product_quants.length; i++) {
|
||||
if (pack_lot_lines.product_quants[i].lot_id[1] == lot_name) {
|
||||
quant = pack_lot_lines.product_quants[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (quant) {
|
||||
pack_line.set_quant(quant);
|
||||
} else {
|
||||
pack_line.set_lot_name(lot_name);
|
||||
}
|
||||
});
|
||||
pack_lot_lines.remove_empty_model();
|
||||
pack_lot_lines.set_quantity_by_lot();
|
||||
this.options.order.save_to_db();
|
||||
this.options.order_line.trigger('change', this.options.order_line);
|
||||
this.gui.close_popup();
|
||||
},
|
||||
|
||||
this.gui.popup_instances.packlotline = packlotline;
|
||||
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
157
pos_product_catch_weight/static/src/js/models.js
Executable file
157
pos_product_catch_weight/static/src/js/models.js
Executable file
@@ -0,0 +1,157 @@
|
||||
/* Copyright 2018 Tecnativa - David Vidal
|
||||
Copyright 2018 Hibou Corp. - Jared Kipe
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
odoo.define("pos_product_catch_weight.models", function (require) {
|
||||
"use strict";
|
||||
|
||||
var models = require("point_of_sale.models");
|
||||
var session = require("web.session");
|
||||
var utils = require('web.utils');
|
||||
var round_pr = utils.round_precision;
|
||||
|
||||
models.PosModel = models.PosModel.extend({
|
||||
get_lot: function (product, location_id) {
|
||||
var done = new $.Deferred();
|
||||
session.rpc("/web/dataset/search_read", {
|
||||
"model": "stock.quant",
|
||||
"domain": [
|
||||
["location_id", "=", location_id],
|
||||
["product_id", "=", product],
|
||||
["lot_id", "!=", false]],
|
||||
"fields": [
|
||||
'lot_id',
|
||||
'quantity',
|
||||
'lot_catch_weight',
|
||||
'lot_catch_weight_ratio',
|
||||
'lot_catch_weight_uom_id',
|
||||
]
|
||||
}, {'async': false}).then(function (result) {
|
||||
var product_quants = [];
|
||||
if (result.length) {
|
||||
product_quants = result.records;
|
||||
}
|
||||
done.resolve(product_quants);
|
||||
});
|
||||
return done;
|
||||
},
|
||||
});
|
||||
|
||||
var _orderline_super = models.Orderline.prototype;
|
||||
models.Orderline = models.Orderline.extend({
|
||||
compute_lot_lines: function(){
|
||||
var done = new $.Deferred();
|
||||
var compute_lot_lines = _orderline_super.compute_lot_lines.apply(this, arguments);
|
||||
this.pos.get_lot(this.product.id, this.pos.config.stock_location_id[0])
|
||||
.then(function (product_quants) {
|
||||
compute_lot_lines.product_quants = product_quants;
|
||||
done.resolve(compute_lot_lines);
|
||||
});
|
||||
return compute_lot_lines;
|
||||
},
|
||||
|
||||
get_base_price: function(){
|
||||
var rounding = this.pos.currency.rounding;
|
||||
var lot_ratio_sum = 0.0;
|
||||
|
||||
if (this.pack_lot_lines && this.pack_lot_lines.get_valid_lots) {
|
||||
var valid_product_lot = this.pack_lot_lines.get_valid_lots();
|
||||
for (var i=0; i < valid_product_lot.length; i++) {
|
||||
lot_ratio_sum += valid_product_lot[i].get('lot_catch_weight_ratio');
|
||||
}
|
||||
}
|
||||
|
||||
var qty = this.get_quantity();
|
||||
if (lot_ratio_sum != 0.0) {
|
||||
qty = lot_ratio_sum;
|
||||
}
|
||||
return round_pr(this.get_unit_price() * qty * (1 - this.get_discount()/100), rounding);
|
||||
},
|
||||
|
||||
get_all_prices: function(){
|
||||
var lot_ratio_sum = 0.0;
|
||||
|
||||
if (this.pack_lot_lines && this.pack_lot_lines.get_valid_lots) {
|
||||
var valid_product_lot = this.pack_lot_lines.get_valid_lots();
|
||||
for (var i=0; i < valid_product_lot.length; i++) {
|
||||
lot_ratio_sum += valid_product_lot[i].get('lot_catch_weight_ratio');
|
||||
}
|
||||
}
|
||||
|
||||
var qty = this.get_quantity();
|
||||
var qty_ratio = 1.0
|
||||
if (lot_ratio_sum != 0.0) {
|
||||
qty_ratio = lot_ratio_sum / qty;
|
||||
}
|
||||
|
||||
var price_unit = (this.get_unit_price() * qty_ratio) * (1.0 - (this.get_discount() / 100.0));
|
||||
|
||||
var taxtotal = 0;
|
||||
|
||||
var product = this.get_product();
|
||||
var taxes_ids = product.taxes_id;
|
||||
var taxes = this.pos.taxes;
|
||||
var taxdetail = {};
|
||||
var product_taxes = [];
|
||||
|
||||
_(taxes_ids).each(function(el){
|
||||
product_taxes.push(_.detect(taxes, function(t){
|
||||
return t.id === el;
|
||||
}));
|
||||
});
|
||||
|
||||
var all_taxes = this.compute_all(product_taxes, price_unit, this.get_quantity(), this.pos.currency.rounding);
|
||||
_(all_taxes.taxes).each(function(tax) {
|
||||
taxtotal += tax.amount;
|
||||
taxdetail[tax.id] = tax.amount;
|
||||
});
|
||||
|
||||
return {
|
||||
"priceWithTax": all_taxes.total_included,
|
||||
"priceWithoutTax": all_taxes.total_excluded,
|
||||
"tax": taxtotal,
|
||||
"taxDetails": taxdetail,
|
||||
};
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
//var _packlotline_super = models.Packlotline.prototype;
|
||||
models.Packlotline = models.Packlotline.extend({
|
||||
defaults: {
|
||||
lot_name: null,
|
||||
lot_catch_weight_ratio: 1.0,
|
||||
lot_catch_weight: 1.0,
|
||||
lot_catch_weight_uom_id: null,
|
||||
},
|
||||
|
||||
set_quant: function(quant) {
|
||||
this.set({
|
||||
lot_name: _.str.trim(quant.lot_id[1]) || null,
|
||||
lot_catch_weight_ratio: quant.lot_catch_weight_ratio || 1.0,
|
||||
lot_catch_weight: quant.lot_catch_weight || 1.0,
|
||||
lot_catch_weight_uom_id: quant.lot_catch_weight_uom_id || null,
|
||||
});
|
||||
},
|
||||
|
||||
set_lot_name: function(name){
|
||||
/*
|
||||
If you want to allow selling unknown lots:
|
||||
{
|
||||
lot_name : _.str.trim(name) || null,
|
||||
lot_catch_weight_ratio : 1.0,
|
||||
lot_catch_weight : 1.0,
|
||||
lot_catch_weight_uom_id : null,
|
||||
}
|
||||
*/
|
||||
|
||||
this.set({
|
||||
lot_name : _.str.trim(name) || null,
|
||||
lot_catch_weight_ratio : 9999.0,
|
||||
lot_catch_weight : 9999.0,
|
||||
lot_catch_weight_uom_id : [0, 'INVALID'],
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
});
|
||||
59
pos_product_catch_weight/static/src/xml/pos.xml
Executable file
59
pos_product_catch_weight/static/src/xml/pos.xml
Executable file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2018 Tecnativa - David Vidal
|
||||
Copyright 2018 Hibou Corp. - Jared Kipe
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<templates>
|
||||
|
||||
<t t-extend="PackLotLinePopupWidget">
|
||||
<t t-jquery=".title" t-operation="append">
|
||||
<div class="packlot-select">
|
||||
<t t-if="widget.options.pack_lot_lines and widget.options.pack_lot_lines.product_quants">
|
||||
<select class="packlot-line-select">
|
||||
<option disabled="" selected="" value="">Select a Serial/Lot Number</option>
|
||||
<t t-foreach="widget.options.pack_lot_lines.product_quants" t-as="q">
|
||||
<option t-att-value="q.lot_id[1]">
|
||||
<t t-esc="q.lot_id[1]"/> : <t t-esc="q.lot_catch_weight"/> <t t-esc="q.lot_catch_weight_uom_id[1]"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="Orderline">
|
||||
<t t-jquery=".info-list" t-operation="append">
|
||||
<t t-if="line.pack_lot_lines and line.pack_lot_lines.get_valid_lots">
|
||||
<t t-set="lots" t-value="line.pack_lot_lines.get_valid_lots()"/>
|
||||
<t t-if="lots" t-foreach="lots" t-as="lot">
|
||||
<li class="info">
|
||||
-- <t t-esc="lot.get_lot_name()"/>
|
||||
<t t-if="lot.get('lot_catch_weight_uom_id')">
|
||||
@ <t t-esc="lot.get('lot_catch_weight')"/>
|
||||
<t t-esc="lot.get('lot_catch_weight_uom_id')[1]"/>
|
||||
</t>
|
||||
</li>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="PosTicket">
|
||||
<t t-jquery=".receipt-orderlines tr td:nth-child(1)" t-operation="append">
|
||||
<t t-if="orderline.pack_lot_lines and orderline.pack_lot_lines.get_valid_lots">
|
||||
<br/>
|
||||
<t t-set="lots" t-value="orderline.pack_lot_lines.get_valid_lots()"/>
|
||||
<t t-if="lots" t-foreach="lots" t-as="lot">
|
||||
-- <t t-esc="lot.get_lot_name()"/>
|
||||
<t t-if="lot.get('lot_catch_weight_uom_id')">
|
||||
@ <t t-esc="lot.get('lot_catch_weight')"/>
|
||||
<t t-esc="lot.get('lot_catch_weight_uom_id')[1]"/>
|
||||
</t>
|
||||
<br/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
16
pos_product_catch_weight/templates/assets.xml
Executable file
16
pos_product_catch_weight/templates/assets.xml
Executable file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2018 Tecnativa - David Vidal
|
||||
Copyright 2018 Hibou Corp. - Jared Kipe
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<odoo>
|
||||
|
||||
<template id="assets" inherit_id="point_of_sale.assets">
|
||||
<xpath expr=".">
|
||||
<script type="text/javascript" src="/pos_product_catch_weight/static/src/js/models.js"/>
|
||||
<script type="text/javascript" src="/pos_product_catch_weight/static/src/js/chrome.js"/>
|
||||
<link rel="stylesheet" href="/pos_product_catch_weight/static/src/css/pos.css" />
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
38
pos_product_catch_weight/views/pos_views.xml
Normal file
38
pos_product_catch_weight/views/pos_views.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="view_pos_order_line_form_inherit" model="ir.ui.view">
|
||||
<field name="name">pos.order.line.form.inherit</field>
|
||||
<field name="model">pos.order.line</field>
|
||||
<field name="inherit_id" ref="point_of_sale.view_pos_order_line_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group" position="after">
|
||||
<group col="4">
|
||||
<field name="pack_lot_ids" colspan="4">
|
||||
<tree>
|
||||
<field name="lot_name"/>
|
||||
<field name="lot_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_pos_pos_form_inherit" model="ir.ui.view">
|
||||
<field name="name">pos.order.form.inherit</field>
|
||||
<field name="model">pos.order</field>
|
||||
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='lines']/form/group" position="after">
|
||||
<group col="4">
|
||||
<field name="pack_lot_ids" colspan="4">
|
||||
<tree>
|
||||
<field name="lot_name"/>
|
||||
<field name="lot_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -25,10 +25,14 @@ class AccountInvoiceLine(models.Model):
|
||||
move_lines = self.sale_line_ids.mapped('move_ids.move_line_ids')
|
||||
else:
|
||||
move_lines = self.purchase_line_id.mapped('move_ids.move_line_ids')
|
||||
for move_line in move_lines:
|
||||
for move_line in move_lines.filtered(lambda l: l.lot_id):
|
||||
qty_done = move_line.qty_done
|
||||
current_qty_done = qty_done + qty_done_total
|
||||
r = move_line.lot_id.catch_weight_ratio
|
||||
ratio = ((ratio * qty_done_total) + (qty_done * r)) / (qty_done + qty_done_total)
|
||||
if current_qty_done == 0:
|
||||
ratio = 0
|
||||
else:
|
||||
ratio = ((ratio * qty_done_total) + (qty_done * r)) / current_qty_done
|
||||
qty_done_total += qty_done
|
||||
catch_weight += move_line.lot_id.catch_weight
|
||||
price = price * ratio
|
||||
|
||||
@@ -8,7 +8,6 @@ class StockProductionLot(models.Model):
|
||||
catch_weight = fields.Float(string='Catch Weight', digits=(10, 4))
|
||||
catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id')
|
||||
|
||||
|
||||
@api.depends('catch_weight')
|
||||
def _compute_catch_weight_ratio(self):
|
||||
for lot in self:
|
||||
@@ -44,3 +43,22 @@ class StockMoveLine(models.Model):
|
||||
catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM')
|
||||
lot_catch_weight = fields.Float(related='lot_id.catch_weight')
|
||||
lot_catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id')
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
has_catch_weight = fields.Boolean(string="Has Catch Weight", compute='_compute_has_catch_weight', store=True)
|
||||
|
||||
@api.depends('move_lines.product_catch_weight_uom_id')
|
||||
def _compute_has_catch_weight(self):
|
||||
for picking in self:
|
||||
picking.has_catch_weight = any(picking.mapped('move_lines.product_catch_weight_uom_id'))
|
||||
|
||||
|
||||
class StockQuant(models.Model):
|
||||
_inherit = 'stock.quant'
|
||||
|
||||
lot_catch_weight_ratio = fields.Float(related='lot_id.catch_weight_ratio')
|
||||
lot_catch_weight = fields.Float(related='lot_id.catch_weight')
|
||||
lot_catch_weight_uom_id = fields.Many2one('product.uom', related='lot_id.catch_weight_uom_id')
|
||||
|
||||
@@ -30,6 +30,7 @@ class TestPicking(TransactionCase):
|
||||
'uom_po_id': self.product_uom_id.id,
|
||||
'catch_weight_uom_id': self.ref_uom_id.id,
|
||||
})
|
||||
self.pricelist = self.env.ref('product.list0')
|
||||
|
||||
|
||||
# def test_creation(self):
|
||||
@@ -69,6 +70,7 @@ class TestPicking(TransactionCase):
|
||||
'partner_invoice_id': self.partner1.id,
|
||||
'partner_shipping_id': self.partner1.id,
|
||||
'order_line': [(0, 0, {'product_id': self.product1.id})],
|
||||
'pricelist_id': self.pricelist.id,
|
||||
})
|
||||
so.action_confirm()
|
||||
self.assertTrue(so.state in ('sale', 'done'))
|
||||
@@ -104,6 +106,7 @@ class TestPicking(TransactionCase):
|
||||
'partner_invoice_id': self.partner1.id,
|
||||
'partner_shipping_id': self.partner1.id,
|
||||
'order_line': [(0, 0, {'product_id': self.product1.id, 'product_uom_qty': 2.0})],
|
||||
'pricelist_id': self.pricelist.id,
|
||||
})
|
||||
so.action_confirm()
|
||||
self.assertTrue(so.state in ('sale', 'done'))
|
||||
|
||||
@@ -13,16 +13,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--<record id="view_move_line_form_inherit" model="ir.ui.view">-->
|
||||
<!--<field name="name">stock.move.line.form.inherit</field>-->
|
||||
<!--<field name="model">stock.move.line</field>-->
|
||||
<!--<field name="inherit_id" ref="stock.view_move_line_form" />-->
|
||||
<!--<field name="arch" type="xml">-->
|
||||
<!--<xpath expr="//field[@name='lot_name']" position="after">-->
|
||||
<!--<field name="lot_catch_weight_ratio" readonly="1"/>-->
|
||||
<!--</xpath>-->
|
||||
<!--</field>-->
|
||||
<!--</record>-->
|
||||
<record id="view_stock_move_operations_inherit" model="ir.ui.view">
|
||||
<field name="name">stock.move.operations.form.inherit</field>
|
||||
<field name="model">stock.move</field>
|
||||
@@ -36,6 +26,7 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_stock_move_line_operation_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">stock.move.line.operations.tree.inherit</field>
|
||||
<field name="model">stock.move.line</field>
|
||||
@@ -49,6 +40,7 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">product.template.common.form.inherit</field>
|
||||
<field name="model">product.template</field>
|
||||
@@ -59,4 +51,18 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="stock_view_picking_internal_search_inherit" model="ir.ui.view">
|
||||
<field name="name">stock.view.picking.internal.search.inherit</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_internal_search" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='done']" position="after">
|
||||
<filter name="has_catch_weight" string="Has Catch Weight" domain="[('has_catch_weight','=',True)]" help="Pickings with Catch Weight"/>
|
||||
</xpath>
|
||||
<xpath expr="//group/filter[@name='picking_type']" position="after">
|
||||
<filter string="Has Catch Weight" domain="[]" context="{'group_by':'has_catch_weight'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="line_ids">
|
||||
<tree editable="top">
|
||||
<tree editable="top" create="false" delete="false">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="qty_ordered" readonly="1"/>
|
||||
<field name="qty_delivered" readonly="1"/>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="line_ids">
|
||||
<tree editable="top">
|
||||
<tree editable="top" create="false" delete="false">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="qty_ordered" readonly="1"/>
|
||||
<field name="qty_delivered" readonly="1"/>
|
||||
|
||||
1
sale_line_change/__init__.py
Normal file
1
sale_line_change/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import wizard
|
||||
25
sale_line_change/__manifest__.py
Normal file
25
sale_line_change/__manifest__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
'name': 'Sale Line Change',
|
||||
'summary': 'Change Confirmed Sale Lines Routes or Warehouses.',
|
||||
'version': '11.0.1.0.0',
|
||||
'author': "Hibou Corp.",
|
||||
'category': 'Sale',
|
||||
'license': 'AGPL-3',
|
||||
'complexity': 'expert',
|
||||
'images': [],
|
||||
'website': "https://hibou.io",
|
||||
'description': """
|
||||
""",
|
||||
'depends': [
|
||||
'sale_sourced_by_line',
|
||||
'sale_stock',
|
||||
'stock_dropshipping',
|
||||
],
|
||||
'demo': [],
|
||||
'data': [
|
||||
'wizard/sale_line_change_views.xml',
|
||||
'views/sale_views.xml',
|
||||
],
|
||||
'auto_install': False,
|
||||
'installable': True,
|
||||
}
|
||||
1
sale_line_change/tests/__init__.py
Normal file
1
sale_line_change/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_sale_line_change
|
||||
99
sale_line_change/tests/test_sale_line_change.py
Normal file
99
sale_line_change/tests/test_sale_line_change.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from odoo.tests import common
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class TestSaleLineChange(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestSaleLineChange, self).setUp()
|
||||
self.warehouse0 = self.env.ref('stock.warehouse0')
|
||||
self.warehouse1 = self.env['stock.warehouse'].create({
|
||||
'company_id': self.env.user.company_id.id,
|
||||
# 'partner_id': self.env.user.company_id.partner_id.id,
|
||||
'name': 'TWH1',
|
||||
'code': 'TWH1',
|
||||
})
|
||||
self.product1 = self.env.ref('product.product_product_24')
|
||||
self.partner1 = self.env.ref('base.res_partner_12')
|
||||
self.so1 = self.env['sale.order'].create({
|
||||
'partner_id': self.partner1.id,
|
||||
'partner_invoice_id': self.partner1.id,
|
||||
'partner_shipping_id': self.partner1.id,
|
||||
'order_line': [(0, 0, {
|
||||
'product_id': self.product1.id,
|
||||
'name': 'N/A',
|
||||
'product_uom_qty': 1.0,
|
||||
'price_unit': 100.0,
|
||||
})]
|
||||
})
|
||||
self.dropship_route = self.env.ref('stock_dropshipping.route_drop_shipping')
|
||||
self.warehouse0_route = self.warehouse0.route_ids.filtered(lambda r: r.name.find('Ship') >= 0)
|
||||
|
||||
def test_00_sale_change_warehouse(self):
|
||||
so = self.so1
|
||||
|
||||
so.action_confirm()
|
||||
self.assertTrue(so.state in ('sale', 'done'))
|
||||
self.assertTrue(so.picking_ids)
|
||||
org_picking = so.picking_ids
|
||||
self.assertEqual(org_picking.picking_type_id.warehouse_id, self.warehouse0)
|
||||
|
||||
wiz = self.env['sale.line.change.order'].with_context(default_order_id=so.id).create({})
|
||||
self.assertTrue(wiz.line_ids)
|
||||
wiz.line_ids.line_warehouse_id = self.warehouse1
|
||||
wiz.line_ids.line_date_planned = '2018-01-01 00:00:00'
|
||||
wiz.apply()
|
||||
|
||||
self.assertTrue(len(so.picking_ids) == 2)
|
||||
self.assertTrue(org_picking.state == 'cancel')
|
||||
new_picking = so.picking_ids - org_picking
|
||||
self.assertTrue(new_picking)
|
||||
self.assertEqual(new_picking.picking_type_id.warehouse_id, self.warehouse1)
|
||||
self.assertEqual(new_picking.scheduled_date, '2018-01-01 00:00:00')
|
||||
|
||||
def test_01_sale_change_route(self):
|
||||
so = self.so1
|
||||
|
||||
so.action_confirm()
|
||||
self.assertTrue(so.state in ('sale', 'done'))
|
||||
self.assertTrue(so.picking_ids)
|
||||
org_picking = so.picking_ids
|
||||
self.assertEqual(org_picking.picking_type_id.warehouse_id, self.warehouse0)
|
||||
|
||||
# Change route on wizard line
|
||||
wiz = self.env['sale.line.change.order'].with_context(default_order_id=so.id).create({})
|
||||
self.assertTrue(wiz.line_ids)
|
||||
wiz.line_ids.line_route_id = self.dropship_route
|
||||
wiz.apply()
|
||||
|
||||
# Check that RFQ/PO was created.
|
||||
self.assertTrue(org_picking.state == 'cancel')
|
||||
po_line = self.env['purchase.order.line'].search([('sale_line_id', '=', so.order_line.id)])
|
||||
self.assertTrue(po_line)
|
||||
|
||||
def test_02_sale_dropshipping_to_warehouse(self):
|
||||
self.assertTrue(self.warehouse0_route)
|
||||
self.product1.route_ids += self.dropship_route
|
||||
so = self.so1
|
||||
|
||||
so.action_confirm()
|
||||
self.assertTrue(so.state in ('sale', 'done'))
|
||||
self.assertFalse(so.picking_ids)
|
||||
|
||||
# Change route on wizard line
|
||||
wiz = self.env['sale.line.change.order'].with_context(default_order_id=so.id).create({})
|
||||
self.assertTrue(wiz.line_ids)
|
||||
wiz.line_ids.line_route_id = self.warehouse0_route
|
||||
wiz.line_ids.line_date_planned = '2018-01-01 00:00:00'
|
||||
|
||||
# Wizard cannot complete because of non-cancelled Purchase Order.
|
||||
with self.assertRaises(ValidationError):
|
||||
wiz.apply()
|
||||
|
||||
po_line = self.env['purchase.order.line'].search([('sale_line_id', '=', so.order_line.id)])
|
||||
po_line.order_id.button_cancel()
|
||||
wiz.apply()
|
||||
|
||||
# Check parameters on new picking
|
||||
self.assertTrue(so.picking_ids)
|
||||
self.assertEqual(so.picking_ids.picking_type_id.warehouse_id, self.warehouse0)
|
||||
self.assertEqual(so.picking_ids.scheduled_date, '2018-01-01 00:00:00')
|
||||
19
sale_line_change/views/sale_views.xml
Normal file
19
sale_line_change/views/sale_views.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="view_order_form_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.order.form.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header/button[@name='action_confirm']" position="after">
|
||||
<button name="%(action_sale_line_change_order)d"
|
||||
type="action"
|
||||
attrs="{'invisible': [('state', 'in', ('draft', 'sent', 'cancel'))]}"
|
||||
string="Line Change"
|
||||
class="btn-secondary"
|
||||
context="{'default_order_id': active_id}"
|
||||
groups="sales_team.group_sale_manager"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
1
sale_line_change/wizard/__init__.py
Normal file
1
sale_line_change/wizard/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import sale_line_change
|
||||
89
sale_line_change/wizard/sale_line_change.py
Normal file
89
sale_line_change/wizard/sale_line_change.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class SaleLineChangeOrder(models.TransientModel):
|
||||
_name = 'sale.line.change.order'
|
||||
_description = 'Sale Line Change Order'
|
||||
|
||||
order_id = fields.Many2one('sale.order', string='Sale Order')
|
||||
line_ids = fields.One2many('sale.line.change.order.line', 'change_order_id', string='Change Lines')
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
rec = super(SaleLineChangeOrder, self).default_get(fields)
|
||||
if 'order_id' in rec:
|
||||
order = self.env['sale.order'].browse(rec['order_id'])
|
||||
if not order:
|
||||
return rec
|
||||
|
||||
line_model = self.env['sale.line.change.order.line']
|
||||
rec['line_ids'] = [(0, 0, line_model.values_from_so_line(l)) for l in order.order_line]
|
||||
return rec
|
||||
|
||||
@api.multi
|
||||
def apply(self):
|
||||
self.ensure_one()
|
||||
self.line_ids.apply()
|
||||
return True
|
||||
|
||||
|
||||
class SaleLineChangeOrderLine(models.TransientModel):
|
||||
_name = 'sale.line.change.order.line'
|
||||
|
||||
change_order_id = fields.Many2one('sale.line.change.order')
|
||||
sale_line_id = fields.Many2one('sale.order.line', string='Sale Line')
|
||||
line_ordered_qty = fields.Float(string='Ordered Qty')
|
||||
line_delivered_qty = fields.Float(string='Delivered Qty')
|
||||
line_reserved_qty = fields.Float(string='Reserved Qty')
|
||||
line_date_planned = fields.Datetime(string='Planned Date')
|
||||
line_warehouse_id = fields.Many2one('stock.warehouse', string='Warehouse')
|
||||
line_route_id = fields.Many2one('stock.location.route', string='Route')
|
||||
|
||||
def values_from_so_line(self, so_line):
|
||||
move_ids = so_line.move_ids
|
||||
reserved_qty = sum(move_ids.mapped('reserved_availability'))
|
||||
return {
|
||||
'sale_line_id': so_line.id,
|
||||
'line_ordered_qty': so_line.product_uom_qty,
|
||||
'line_delivered_qty': so_line.qty_delivered,
|
||||
'line_reserved_qty': reserved_qty,
|
||||
'line_date_planned': so_line.date_planned,
|
||||
'line_warehouse_id': so_line.warehouse_id.id,
|
||||
'line_route_id': so_line.route_id.id,
|
||||
}
|
||||
|
||||
def _apply(self):
|
||||
self._apply_clean_dropship()
|
||||
self._apply_clean_existing_moves()
|
||||
self._apply_new_values()
|
||||
self._apply_procurement()
|
||||
|
||||
def _apply_clean_dropship(self):
|
||||
po_line_model = self.env['purchase.order.line'].sudo()
|
||||
po_lines = po_line_model.search([('sale_line_id', 'in', self.mapped('sale_line_id.id'))])
|
||||
|
||||
if po_lines and po_lines.filtered(lambda l: l.order_id.state != 'cancel'):
|
||||
names = ', '.join(po_lines.filtered(lambda l: l.order_id.state != 'cancel').mapped('order_id.name'))
|
||||
raise ValidationError('One or more lines has existing non-cancelled Purchase Orders associated: ' + names)
|
||||
|
||||
def _apply_clean_existing_moves(self):
|
||||
moves = self.mapped('sale_line_id.move_ids').filtered(lambda m: m.state != 'done')
|
||||
moves._action_cancel()
|
||||
|
||||
def _apply_new_values(self):
|
||||
for line in self:
|
||||
line.sale_line_id.write({
|
||||
'date_planned': line.line_date_planned,
|
||||
'warehouse_id': line.line_warehouse_id.id,
|
||||
'route_id': line.line_route_id.id,
|
||||
})
|
||||
|
||||
def _apply_procurement(self):
|
||||
self.mapped('sale_line_id')._action_launch_procurement_rule()
|
||||
|
||||
def apply(self):
|
||||
changed_lines = self.filtered(lambda l: (
|
||||
l.sale_line_id.warehouse_id != l.line_warehouse_id
|
||||
or l.sale_line_id.route_id != l.line_route_id))
|
||||
changed_lines._apply()
|
||||
40
sale_line_change/wizard/sale_line_change_views.xml
Normal file
40
sale_line_change/wizard/sale_line_change_views.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="sale_line_change_order_form" model="ir.ui.view">
|
||||
<field name="name">sale.line.change.order.form</field>
|
||||
<field name="model">sale.line.change.order</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<p>Changing Date Planned alone should be done on any existing Pickings or POs.</p>
|
||||
<field name="order_id" invisible="1"/>
|
||||
<field name="line_ids">
|
||||
<tree editable="top" create="false" delete="false">
|
||||
<field name="sale_line_id" string="Line" readonly="1" force_save="1"/>
|
||||
<field name="line_ordered_qty" string="Ordered" readonly="1"/>
|
||||
<field name="line_delivered_qty" string="Delivered" readonly="1"/>
|
||||
<field name="line_reserved_qty" string="Reserved" readonly="1"/>
|
||||
<field name="line_date_planned"/>
|
||||
<field name="line_warehouse_id" options="{'no_create': True,}"/>
|
||||
<field name="line_route_id" domain="[('sale_selectable', '=', True)]" options="{'no_create': True,}"/>
|
||||
</tree>
|
||||
</field>
|
||||
<footer>
|
||||
<button name="apply" type="object" string="Apply Changes" class="btn-primary"/>
|
||||
<button class="oe_link"
|
||||
special="cancel"
|
||||
string="Cancel" />
|
||||
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_sale_line_change_order" model="ir.actions.act_window">
|
||||
<field name="name">Sale Line Change Order</field>
|
||||
<field name="res_model">sale.line.change.order</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="sale_line_change_order_form" />
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -15,8 +15,8 @@ class Partner(models.Model):
|
||||
for partner in self.with_context(lang='en_US'):
|
||||
if ZipcodeSearchEngine and partner.zip:
|
||||
with ZipcodeSearchEngine() as search:
|
||||
zipcode = search.by_zipcode(partner.zip)
|
||||
if zipcode:
|
||||
zipcode = search.by_zipcode(str(self.zip).split('-')[0])
|
||||
if zipcode and zipcode['Latitude']:
|
||||
partner.write({
|
||||
'partner_latitude': zipcode['Latitude'],
|
||||
'partner_longitude': zipcode['Longitude'],
|
||||
|
||||
@@ -55,7 +55,7 @@ class FakePartner():
|
||||
|
||||
@property
|
||||
def date_localization(self):
|
||||
if not hasattr(self, 'date_localization'):
|
||||
if not hasattr(self, 'date_localization') and self.date_localization:
|
||||
self.date_localization = 'TODAY!'
|
||||
# The fast way.
|
||||
if ZipcodeSearchEngine and self.zip:
|
||||
|
||||
1
stock_landed_costs_average/__init__.py
Normal file
1
stock_landed_costs_average/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
21
stock_landed_costs_average/__manifest__.py
Normal file
21
stock_landed_costs_average/__manifest__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
'name': 'Landed Costs Average',
|
||||
'summary': 'Use Landed Costs on Average Cost inventory.',
|
||||
'version': '11.0.1.0.0',
|
||||
'author': "Hibou Corp. <hello@hibou.io>",
|
||||
'category': 'Warehouse',
|
||||
'license': 'AGPL-3',
|
||||
'complexity': 'expert',
|
||||
'images': [],
|
||||
'website': "https://hibou.io",
|
||||
'description': """
|
||||
""",
|
||||
'depends': [
|
||||
'stock_landed_costs',
|
||||
],
|
||||
'demo': [],
|
||||
'data': [
|
||||
],
|
||||
'auto_install': False,
|
||||
'installable': True,
|
||||
}
|
||||
1
stock_landed_costs_average/models/__init__.py
Normal file
1
stock_landed_costs_average/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import stock_landed_cost
|
||||
96
stock_landed_costs_average/models/stock_landed_cost.py
Normal file
96
stock_landed_costs_average/models/stock_landed_cost.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LandedCost(models.Model):
|
||||
_inherit = 'stock.landed.cost'
|
||||
|
||||
def get_valuation_lines(self):
|
||||
"""
|
||||
Override for allowing Average value inventory.
|
||||
:return: list of new line values
|
||||
"""
|
||||
lines = []
|
||||
|
||||
for move in self.mapped('picking_ids').mapped('move_lines'):
|
||||
# Only allow for real time valuated products with 'average' or 'fifo' cost
|
||||
if move.product_id.valuation != 'real_time' or move.product_id.cost_method not in ('fifo', 'average'):
|
||||
continue
|
||||
vals = {
|
||||
'product_id': move.product_id.id,
|
||||
'move_id': move.id,
|
||||
'quantity': move.product_qty,
|
||||
'former_cost': move.value,
|
||||
'weight': move.product_id.weight * move.product_qty,
|
||||
'volume': move.product_id.volume * move.product_qty
|
||||
}
|
||||
lines.append(vals)
|
||||
|
||||
if not lines and self.mapped('picking_ids'):
|
||||
raise UserError(_('The selected picking does not contain any move that would be impacted by landed costs. Landed costs are only possible for products configured in real time valuation with real price costing method. Please make sure it is the case, or you selected the correct picking'))
|
||||
return lines
|
||||
|
||||
@api.multi
|
||||
def button_validate(self):
|
||||
"""
|
||||
Override to directly set new standard_price on product if average costed.
|
||||
:return: True
|
||||
"""
|
||||
if any(cost.state != 'draft' for cost in self):
|
||||
raise UserError(_('Only draft landed costs can be validated'))
|
||||
if any(not cost.valuation_adjustment_lines for cost in self):
|
||||
raise UserError(_('No valuation adjustments lines. You should maybe recompute the landed costs.'))
|
||||
if not self._check_sum():
|
||||
raise UserError(_('Cost and adjustments lines do not match. You should maybe recompute the landed costs.'))
|
||||
|
||||
for cost in self:
|
||||
move = self.env['account.move']
|
||||
move_vals = {
|
||||
'journal_id': cost.account_journal_id.id,
|
||||
'date': cost.date,
|
||||
'ref': cost.name,
|
||||
'line_ids': [],
|
||||
}
|
||||
for line in cost.valuation_adjustment_lines.filtered(lambda line: line.move_id):
|
||||
# Prorate the value at what's still in stock
|
||||
_logger.warn('(line.move_id.remaining_qty / line.move_id.product_qty) * line.additional_landed_cost')
|
||||
_logger.warn('(%s / %s) * %s' % (line.move_id.remaining_qty, line.move_id.product_qty, line.additional_landed_cost))
|
||||
cost_to_add = (line.move_id.remaining_qty / line.move_id.product_qty) * line.additional_landed_cost
|
||||
_logger.warn('cost_to_add: ' + str(cost_to_add))
|
||||
|
||||
new_landed_cost_value = line.move_id.landed_cost_value + line.additional_landed_cost
|
||||
line.move_id.write({
|
||||
'landed_cost_value': new_landed_cost_value,
|
||||
'value': line.move_id.value + cost_to_add,
|
||||
'remaining_value': line.move_id.remaining_value + cost_to_add,
|
||||
'price_unit': (line.move_id.value + new_landed_cost_value) / line.move_id.product_qty,
|
||||
})
|
||||
# `remaining_qty` is negative if the move is out and delivered products that were not
|
||||
# in stock.
|
||||
qty_out = 0
|
||||
if line.move_id._is_in():
|
||||
qty_out = line.move_id.product_qty - line.move_id.remaining_qty
|
||||
elif line.move_id._is_out():
|
||||
qty_out = line.move_id.product_qty
|
||||
move_vals['line_ids'] += line._create_accounting_entries(move, qty_out)
|
||||
|
||||
# Need to set the standard price directly on the product.
|
||||
if line.product_id.cost_method == 'average':
|
||||
# From product.do_change_standard_price
|
||||
quant_locs = self.env['stock.quant'].sudo().read_group([('product_id', '=', line.product_id.id)],
|
||||
['location_id'], ['location_id'])
|
||||
quant_loc_ids = [loc['location_id'][0] for loc in quant_locs]
|
||||
locations = self.env['stock.location'].search(
|
||||
[('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id),
|
||||
('id', 'in', quant_loc_ids)])
|
||||
qty_available = line.product_id.with_context(location=locations.ids).qty_available
|
||||
total_cost = (qty_available * line.product_id.standard_price) + cost_to_add
|
||||
line.product_id.write({'standard_price': total_cost / qty_available})
|
||||
|
||||
move = move.create(move_vals)
|
||||
cost.write({'state': 'done', 'account_move_id': move.id})
|
||||
move.post()
|
||||
return True
|
||||
1
stock_landed_costs_average/tests/__init__.py
Normal file
1
stock_landed_costs_average/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_stock_landed_cost
|
||||
102
stock_landed_costs_average/tests/test_stock_landed_cost.py
Normal file
102
stock_landed_costs_average/tests/test_stock_landed_cost.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from odoo.addons.stock_landed_costs.tests.test_stock_landed_costs_purchase import TestLandedCosts
|
||||
|
||||
|
||||
class TestLandedCostsAverage(TestLandedCosts):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLandedCostsAverage, self).setUp()
|
||||
self.product_refrigerator.cost_method = 'average'
|
||||
self.product_oven.cost_method = 'average'
|
||||
|
||||
def test_00_landed_costs_on_incoming_shipment(self):
|
||||
original_standard_price = self.product_refrigerator.standard_price
|
||||
super(TestLandedCostsAverage, self).test_00_landed_costs_on_incoming_shipment()
|
||||
self.assertTrue(original_standard_price != self.product_refrigerator.standard_price)
|
||||
|
||||
def test_01_landed_costs_simple_average(self):
|
||||
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
|
||||
self.assertEqual(self.product_refrigerator.qty_available, 0.0)
|
||||
picking_in = self.Picking.create({
|
||||
'partner_id': self.supplier_id,
|
||||
'picking_type_id': self.picking_type_in_id,
|
||||
'location_id': self.supplier_location_id,
|
||||
'location_dest_id': self.stock_location_id})
|
||||
self.Move.create({
|
||||
'name': self.product_refrigerator.name,
|
||||
'product_id': self.product_refrigerator.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.product_refrigerator.uom_id.id,
|
||||
'picking_id': picking_in.id,
|
||||
'location_id': self.supplier_location_id,
|
||||
'location_dest_id': self.stock_location_id})
|
||||
picking_in.action_confirm()
|
||||
res_dict = picking_in.button_validate()
|
||||
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
|
||||
wizard.process()
|
||||
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
|
||||
self.assertEqual(self.product_refrigerator.qty_available, 5.0)
|
||||
|
||||
stock_landed_cost = self._create_landed_costs({
|
||||
'equal_price_unit': 50,
|
||||
'quantity_price_unit': 0,
|
||||
'weight_price_unit': 0,
|
||||
'volume_price_unit': 0}, picking_in)
|
||||
stock_landed_cost.compute_landed_cost()
|
||||
stock_landed_cost.button_validate()
|
||||
account_entry = self.env['account.move.line'].read_group(
|
||||
[('move_id', '=', stock_landed_cost.account_move_id.id)], ['debit', 'credit', 'move_id'], ['move_id'])[0]
|
||||
self.assertEqual(account_entry['debit'], 50.0, 'Wrong Account Entry')
|
||||
self.assertEqual(self.product_refrigerator.standard_price, 11.0)
|
||||
|
||||
def test_02_landed_costs_average(self):
|
||||
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
|
||||
self.assertEqual(self.product_refrigerator.qty_available, 0.0)
|
||||
picking_in = self.Picking.create({
|
||||
'partner_id': self.supplier_id,
|
||||
'picking_type_id': self.picking_type_in_id,
|
||||
'location_id': self.supplier_location_id,
|
||||
'location_dest_id': self.stock_location_id})
|
||||
self.Move.create({
|
||||
'name': self.product_refrigerator.name,
|
||||
'product_id': self.product_refrigerator.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.product_refrigerator.uom_id.id,
|
||||
'picking_id': picking_in.id,
|
||||
'location_id': self.supplier_location_id,
|
||||
'location_dest_id': self.stock_location_id})
|
||||
picking_in.action_confirm()
|
||||
res_dict = picking_in.button_validate()
|
||||
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
|
||||
wizard.process()
|
||||
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
|
||||
self.assertEqual(self.product_refrigerator.qty_available, 5.0)
|
||||
|
||||
picking_out = self.Picking.create({
|
||||
'partner_id': self.customer_id,
|
||||
'picking_type_id': self.picking_type_out_id,
|
||||
'location_id': self.stock_location_id,
|
||||
'location_dest_id': self.customer_location_id})
|
||||
self.Move.create({
|
||||
'name': self.product_refrigerator.name,
|
||||
'product_id': self.product_refrigerator.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.product_refrigerator.uom_id.id,
|
||||
'picking_id': picking_out.id,
|
||||
'location_id': self.stock_location_id,
|
||||
'location_dest_id': self.customer_location_id})
|
||||
picking_out.action_confirm()
|
||||
picking_out.action_assign()
|
||||
res_dict = picking_out.button_validate()
|
||||
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
|
||||
wizard.process()
|
||||
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
|
||||
self.assertEqual(self.product_refrigerator.qty_available, 3.0)
|
||||
|
||||
stock_landed_cost = self._create_landed_costs({
|
||||
'equal_price_unit': 50,
|
||||
'quantity_price_unit': 0,
|
||||
'weight_price_unit': 0,
|
||||
'volume_price_unit': 0}, picking_in)
|
||||
stock_landed_cost.compute_landed_cost()
|
||||
stock_landed_cost.button_validate()
|
||||
self.assertEqual(self.product_refrigerator.standard_price, 11.0)
|
||||
Reference in New Issue
Block a user