Merge commit '9428370987aede5e756cc8c38d796af3296e07a4' into 11.0

This commit is contained in:
Jared Kipe
2018-12-17 13:56:25 -08:00
53 changed files with 1121 additions and 86 deletions

View File

@@ -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

View File

@@ -0,0 +1 @@
from . import models

View 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,
}

View File

@@ -0,0 +1 @@
from . import hr_holiday

View 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

View 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>

View File

@@ -19,10 +19,10 @@ ytd += contract.external_wages
remaining = 7000.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; 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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; 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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 7000.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -19,10 +19,10 @@ ytd += contract.external_wages
remaining = 128400.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; 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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 14000.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 12500.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -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

View File

@@ -17,10 +17,10 @@ ytd += contract.external_wages
remaining = 7000.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -19,10 +19,10 @@ ytd += contract.external_wages
remaining = 33700.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; 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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; 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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; 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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -19,10 +19,10 @@ ytd += contract.external_wages
remaining = 11100.00 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 9500.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 10000.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -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

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 9000.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 8000.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -18,10 +18,10 @@ ytd += contract.external_wages
remaining = 47300.0 - ytd
if remaining &lt;= 0.0:
result = 0
elif remaining &lt; categories.GROSS:
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.GROSS
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>

View File

@@ -0,0 +1 @@
from . import models

View 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,
}

View File

@@ -0,0 +1 @@
from . import pos_order

View 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')

View 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;
}

View 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;
},
});
});

View 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'],
});
},
})
});

View 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>

View 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>

View 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>

View File

@@ -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

View File

@@ -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')

View File

@@ -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'))

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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"/>

View File

@@ -0,0 +1 @@
from . import wizard

View 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,
}

View File

@@ -0,0 +1 @@
from . import test_sale_line_change

View 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')

View 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>

View File

@@ -0,0 +1 @@
from . import sale_line_change

View 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()

View 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>

View File

@@ -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'],

View File

@@ -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:

View File

@@ -0,0 +1 @@
from . import models

View 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,
}

View File

@@ -0,0 +1 @@
from . import stock_landed_cost

View 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

View File

@@ -0,0 +1 @@
from . import test_stock_landed_cost

View 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)