[IMP] stock, purchase, sale: inventory back to basics
Applies various small changes:
- [IMP] stock, stock_account: add dates to stock move name (inventory adjustments)
- [IMP] delivery,stock: add pack type weight
- [IMP] {purchase, sale}_stock: add Incoterm Location field
- [IMP] stock_picking_batch: default show Batch Transfer
- [IMP] purchase: add supplier info with sudo qty 1
- [IMP] sale_purchase: add related PO warning
- [FIX] product: order product labels by product
- [IMP] product: default filter vendor pricelist w/ active products
- [IMP] stock: relax picking locations readonly states
- [IMP] stock: add consumables replenish button
- [IMP] stock_picking_batch: pick type auto batch not active by default
- [IMP] stock: add stock quant import template
- [FIX] sale,stock,mrp,purchase: add uom precision
Task Id: 2764771
COM PR: odoo/odoo#91640
--
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Signed-off-by: Tiffany Chang <tic@odoo.com>
This commit is contained in:
@@ -308,7 +308,7 @@ class DeliveryCarrier(models.Model):
|
||||
for line in order.order_line.filtered(lambda line: not line.is_delivery and not line.display_type):
|
||||
total_cost += self._product_price_to_company_currency(line.product_qty, line.product_id, order.company_id)
|
||||
|
||||
total_weight = order._get_estimated_weight()
|
||||
total_weight = order._get_estimated_weight() + default_package_type.base_weight
|
||||
# If max weight == 0 => division by 0. If this happens, we want to have
|
||||
# more in the max weight than in the total weight, so that it only
|
||||
# creates ONE package with everything.
|
||||
@@ -327,7 +327,7 @@ class DeliveryCarrier(models.Model):
|
||||
|
||||
if picking.is_return_picking:
|
||||
commodities = self._get_commodities_from_stock_move_lines(picking.move_line_ids)
|
||||
weight = picking._get_estimated_weight()
|
||||
weight = picking._get_estimated_weight() + default_package_type.base_weight
|
||||
packages.append(DeliveryPackage(commodities, weight, default_package_type, currency=picking.company_id.currency_id, picking=picking))
|
||||
return packages
|
||||
|
||||
|
||||
@@ -11,10 +11,11 @@ from odoo.exceptions import UserError
|
||||
class StockQuantPackage(models.Model):
|
||||
_inherit = "stock.quant.package"
|
||||
|
||||
@api.depends('quant_ids')
|
||||
@api.depends('quant_ids', 'package_type_id')
|
||||
def _compute_weight(self):
|
||||
for package in self:
|
||||
weight = 0.0
|
||||
# set initial weight to package type base weight
|
||||
weight = package.package_type_id.base_weight or 0.0
|
||||
if self.env.context.get('picking_id'):
|
||||
# TODO: potential bottleneck: N packages = N queries, use groupby ?
|
||||
current_picking_move_line_ids = self.env['stock.move.line'].search([
|
||||
|
||||
@@ -9,25 +9,9 @@ class ChooseDeliveryPackage(models.TransientModel):
|
||||
_name = 'choose.delivery.package'
|
||||
_description = 'Delivery Package Selection Wizard'
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
defaults = super().default_get(fields_list)
|
||||
if 'shipping_weight' in fields_list:
|
||||
picking = self.env['stock.picking'].browse(defaults.get('picking_id'))
|
||||
move_line_ids = picking.move_line_ids.filtered(lambda m:
|
||||
float_compare(m.qty_done, 0.0, precision_rounding=m.product_uom_id.rounding) > 0
|
||||
and not m.result_package_id
|
||||
)
|
||||
total_weight = 0.0
|
||||
for ml in move_line_ids:
|
||||
qty = ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
total_weight += qty * ml.product_id.weight
|
||||
defaults['shipping_weight'] = total_weight
|
||||
return defaults
|
||||
|
||||
picking_id = fields.Many2one('stock.picking', 'Picking')
|
||||
delivery_package_type_id = fields.Many2one('stock.package.type', 'Delivery Package Type', check_company=True)
|
||||
shipping_weight = fields.Float('Shipping Weight')
|
||||
shipping_weight = fields.Float('Shipping Weight', compute='_compute_shipping_weight', store=True, readonly=False)
|
||||
weight_uom_name = fields.Char(string='Weight unit of measure label', compute='_compute_weight_uom_name')
|
||||
company_id = fields.Many2one(related='picking_id.company_id')
|
||||
|
||||
@@ -37,6 +21,20 @@ class ChooseDeliveryPackage(models.TransientModel):
|
||||
for package in self:
|
||||
package.weight_uom_name = weight_uom_id.name
|
||||
|
||||
@api.depends('delivery_package_type_id')
|
||||
def _compute_shipping_weight(self):
|
||||
for rec in self:
|
||||
move_line_ids = rec.picking_id.move_line_ids.filtered(lambda m:
|
||||
float_compare(m.qty_done, 0.0, precision_rounding=m.product_uom_id.rounding) > 0
|
||||
and not m.result_package_id
|
||||
)
|
||||
# Add package weights to shipping weight, package base weight is defined in package.type
|
||||
total_weight = rec.delivery_package_type_id.base_weight or 0.0
|
||||
for ml in move_line_ids:
|
||||
qty = ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
total_weight += qty * ml.product_id.weight
|
||||
rec.shipping_weight = total_weight
|
||||
|
||||
@api.onchange('delivery_package_type_id', 'shipping_weight')
|
||||
def _onchange_package_type_weight(self):
|
||||
if self.delivery_package_type_id.max_weight and self.shipping_weight > self.delivery_package_type_id.max_weight:
|
||||
|
||||
@@ -27,7 +27,7 @@ class ProductTemplate(models.Model):
|
||||
compute='_compute_bom_count', compute_sudo=False)
|
||||
used_in_bom_count = fields.Integer('# of BoM Where is Used',
|
||||
compute='_compute_used_in_bom_count', compute_sudo=False)
|
||||
mrp_product_qty = fields.Float('Manufactured',
|
||||
mrp_product_qty = fields.Float('Manufactured', digits='Product Unit of Measure',
|
||||
compute='_compute_mrp_product_qty', compute_sudo=False)
|
||||
produce_delay = fields.Float(
|
||||
'Manufacturing Lead Time', default=0.0,
|
||||
@@ -97,7 +97,7 @@ class ProductProduct(models.Model):
|
||||
compute='_compute_bom_count', compute_sudo=False)
|
||||
used_in_bom_count = fields.Integer('# BoM Where Used',
|
||||
compute='_compute_used_in_bom_count', compute_sudo=False)
|
||||
mrp_product_qty = fields.Float('Manufactured',
|
||||
mrp_product_qty = fields.Float('Manufactured', digits='Product Unit of Measure',
|
||||
compute='_compute_mrp_product_qty', compute_sudo=False)
|
||||
is_kits = fields.Boolean(compute="_compute_is_kits", compute_sudo=False)
|
||||
|
||||
|
||||
@@ -17,9 +17,13 @@ def _prepare_data(env, data):
|
||||
raise UserError(_('Product model not defined, Please contact your administrator.'))
|
||||
|
||||
total = 0
|
||||
qty_by_product_in = data.get('quantity_by_product')
|
||||
# search for products all at once, ordered by name desc since popitem() used in xml to print the labels
|
||||
# is LIFO, which results in ordering by product name in the report
|
||||
products = Product.search([('id', 'in', [int(p) for p in qty_by_product_in.keys()])], order='name desc')
|
||||
quantity_by_product = defaultdict(list)
|
||||
for p, q in data.get('quantity_by_product').items():
|
||||
product = Product.browse(int(p))
|
||||
for product in products:
|
||||
q = qty_by_product_in[str(product.id)]
|
||||
quantity_by_product[product].append((product.barcode, q))
|
||||
total += q
|
||||
if data.get('custom_barcodes'):
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
<search string="Vendor">
|
||||
<field name="partner_id"/>
|
||||
<field name="product_tmpl_id"/>
|
||||
<filter string="Active Products" name="active_products" domain="['|', ('product_tmpl_id.active', '=', True),('product_id.active', '=', True)]"/>
|
||||
<separator />
|
||||
<filter string="Active" name="active" domain="['|', ('date_end', '=', False), ('date_end', '>=', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Archived" name="archived" domain="[('date_end', '<', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d'))]"/>
|
||||
<group expand="0" string="Group By">
|
||||
@@ -122,7 +124,7 @@
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">product.supplierinfo</field>
|
||||
<field name="view_mode">tree,form,kanban</field>
|
||||
<field name="context">{'visible_product_tmpl_id':False}</field>
|
||||
<field name="context">{'visible_product_tmpl_id': False, 'search_default_active_products': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No vendor pricelist found
|
||||
|
||||
@@ -15,7 +15,7 @@ class ProductTemplate(models.Model):
|
||||
'account.account', string="Price Difference Account", company_dependent=True,
|
||||
help="This account is used in automated inventory valuation to "\
|
||||
"record the price difference between a purchase order and its related vendor bill when validating this vendor bill.")
|
||||
purchased_product_qty = fields.Float(compute='_compute_purchased_product_qty', string='Purchased')
|
||||
purchased_product_qty = fields.Float(compute='_compute_purchased_product_qty', string='Purchased', digits='Product Unit of Measure')
|
||||
purchase_method = fields.Selection([
|
||||
('purchase', 'On ordered quantities'),
|
||||
('receive', 'On received quantities'),
|
||||
@@ -52,7 +52,8 @@ class ProductProduct(models.Model):
|
||||
_name = 'product.product'
|
||||
_inherit = 'product.product'
|
||||
|
||||
purchased_product_qty = fields.Float(compute='_compute_purchased_product_qty', string='Purchased')
|
||||
purchased_product_qty = fields.Float(compute='_compute_purchased_product_qty', string='Purchased',
|
||||
digits='Product Unit of Measure')
|
||||
|
||||
def _compute_purchased_product_qty(self):
|
||||
date_from = fields.Datetime.to_string(fields.Date.context_today(self) - relativedelta(years=1))
|
||||
|
||||
@@ -12,7 +12,7 @@ from odoo import api, fields, models, _
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, format_amount, format_date, formatLang, get_lang, groupby
|
||||
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class PurchaseOrder(models.Model):
|
||||
@@ -492,7 +492,7 @@ class PurchaseOrder(models.Model):
|
||||
return {
|
||||
'partner_id': partner.id,
|
||||
'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
|
||||
'min_qty': 0.0,
|
||||
'min_qty': 1.0,
|
||||
'price': price,
|
||||
'currency_id': currency.id,
|
||||
'delay': 0,
|
||||
@@ -528,10 +528,8 @@ class PurchaseOrder(models.Model):
|
||||
vals = {
|
||||
'seller_ids': [(0, 0, supplierinfo)],
|
||||
}
|
||||
try:
|
||||
line.product_id.write(vals)
|
||||
except AccessError: # no write access rights -> just ignore
|
||||
break
|
||||
# supplier info should be added regardless of the user access rights
|
||||
line.product_id.sudo().write(vals)
|
||||
|
||||
def action_create_invoice(self):
|
||||
"""Create the invoice associated to the PO.
|
||||
|
||||
@@ -327,71 +327,3 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon):
|
||||
|
||||
aml = self.env['account.move.line'].search([('purchase_line_id', '=', purchase_order.order_line.id)])
|
||||
self.assertRecordValues(aml, [{'analytic_account_id': analytic_account_manual.id}])
|
||||
|
||||
def test_multicompany_partner_bank(self):
|
||||
""" Test that in a multiple company environment, the bank account of the invoice
|
||||
is the one corresponding to the active company. """
|
||||
company_a = self.env.user.company_id
|
||||
company_b = self.env['res.company'].create({
|
||||
'name': 'Company B',
|
||||
})
|
||||
self.env.ref('l10n_generic_coa.configurable_chart_template')._load(company_b)
|
||||
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'AAAAA',
|
||||
})
|
||||
partner_bank_a = self.env['res.partner.bank'].create({
|
||||
'acc_number': "BE01 23456789 10",
|
||||
'partner_id': partner.id,
|
||||
'acc_type': 'bank'
|
||||
})
|
||||
partner_bank_b = self.env['res.partner.bank'].create({
|
||||
'acc_number': "BE10 98765432 10",
|
||||
'partner_id': partner.id,
|
||||
'acc_type': 'bank'
|
||||
})
|
||||
partner.bank_ids = [partner_bank_a.id, partner_bank_b.id]
|
||||
partner_bank_a.company_id = company_a
|
||||
partner_bank_b.company_id = company_b
|
||||
|
||||
PurchaseOrder = self.env['purchase.order'].with_context(tracking_disable=True)
|
||||
po_a = PurchaseOrder.with_company(company_a).create({
|
||||
'partner_id': partner.id,
|
||||
'company_id': company_a.id,
|
||||
'currency_id': company_a.currency_id.id,
|
||||
'date_order': '2019-01-01',
|
||||
})
|
||||
po_b = PurchaseOrder.with_company(company_b).create({
|
||||
'partner_id': partner.id,
|
||||
'company_id': company_b.id,
|
||||
'currency_id': company_b.currency_id.id,
|
||||
'date_order': '2019-01-01',
|
||||
})
|
||||
|
||||
vals_pol = {
|
||||
'name': self.product_deliver.name,
|
||||
'product_id': self.product_deliver.id,
|
||||
'product_qty': 10.0,
|
||||
'product_uom': self.product_deliver.uom_id.id,
|
||||
'price_unit': self.product_deliver.list_price,
|
||||
'order_id': po_a.id,
|
||||
'taxes_id': False,
|
||||
}
|
||||
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
|
||||
PurchaseOrderLine.create(vals_pol)
|
||||
vals_pol['order_id'] = po_b.id
|
||||
PurchaseOrderLine.create(vals_pol)
|
||||
|
||||
po_a.button_confirm()
|
||||
po_b.button_confirm()
|
||||
|
||||
po_a.order_line.qty_received = po_b.order_line.qty_received = 10
|
||||
|
||||
po_a.with_company(company_a).action_create_invoice()
|
||||
po_b.with_company(company_b).action_create_invoice()
|
||||
|
||||
self.assertEqual(po_a.invoice_ids.company_id, company_a)
|
||||
self.assertEqual(po_a.invoice_ids.partner_bank_id, partner_bank_a)
|
||||
|
||||
self.assertEqual(po_b.invoice_ids.company_id, company_b)
|
||||
self.assertEqual(po_b.invoice_ids.partner_bank_id, partner_bank_b)
|
||||
|
||||
@@ -18,7 +18,7 @@ class PurchaseOrder(models.Model):
|
||||
return self._get_picking_type(self.env.context.get('company_id') or self.env.company.id)
|
||||
|
||||
incoterm_id = fields.Many2one('account.incoterms', 'Incoterm', states={'done': [('readonly', True)]}, help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")
|
||||
|
||||
incoterm_location = fields.Char(string='Incoterm Location', states={'done': [('readonly', True)]})
|
||||
incoming_picking_count = fields.Integer("Incoming Shipment count", compute='_compute_incoming_picking_count')
|
||||
picking_ids = fields.Many2many('stock.picking', compute='_compute_picking_ids', string='Receptions', copy=False, store=True)
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
<xpath expr="//div[@t-elif='o.date_order']" position="after">
|
||||
<div t-if="o.incoterm_id" class="col-3 bm-2">
|
||||
<strong>Incoterm:</strong>
|
||||
<p t-field="o.incoterm_id.code" class="m-0"/>
|
||||
<p t-if="o.incoterm_location" t-out="'%s %s' % (o.incoterm_id.code, o.incoterm_location)" class="m-0"/>
|
||||
<p t-else="" t-field="o.incoterm_id.code" class="m-0"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -37,7 +38,8 @@
|
||||
<div id="informations" class="row mt16 mb16">
|
||||
<div t-if="o.incoterm_id" class="col-3 bm-2">
|
||||
<strong>Incoterm:</strong>
|
||||
<p t-field="o.incoterm_id.code" class="m-0"/>
|
||||
<p t-if="o.incoterm_location" t-out="'%s %s' % (o.incoterm_id.code, o.incoterm_location)" class="m-0"/>
|
||||
<p t-else="" t-field="o.incoterm_id.code" class="m-0"/>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -370,19 +370,14 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon):
|
||||
self.assertEqual(po.order_line.mapped('qty_received'), [4.0], 'Purchase: no conversion error on receipt in different uom"')
|
||||
|
||||
def test_message_qty_already_received(self):
|
||||
_product = self.env['product.product'].create({
|
||||
'name': 'TempProduct',
|
||||
'type': 'consu',
|
||||
'company_id': self.env.user.company_id.id,
|
||||
})
|
||||
self.env.user.write({'company_id': self.company_data['company'].id})
|
||||
|
||||
_purchase_order = self.env['purchase.order'].create({
|
||||
'company_id': self.env.user.company_id.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': _product.name,
|
||||
'product_id': _product.id,
|
||||
'name': self.product_id_2.name,
|
||||
'product_id': self.product_id_2.id,
|
||||
'product_qty': 25.0,
|
||||
'price_unit': 250.0,
|
||||
})],
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<xpath expr="//page[@name='purchase_delivery_invoice']/group/group" position="inside">
|
||||
<field name="default_location_dest_id_usage" invisible="1"/>
|
||||
<field name="incoterm_id"/>
|
||||
<field name="incoterm_location" />
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='reminder']" position="after">
|
||||
<field name="picking_type_id" domain="[('code','=','incoming'), '|', ('warehouse_id', '=', False), ('warehouse_id.company_id', '=', company_id)]" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
|
||||
|
||||
@@ -8,7 +8,7 @@ from odoo.tools.float_utils import float_round
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
sales_count = fields.Float(compute='_compute_sales_count', string='Sold')
|
||||
sales_count = fields.Float(compute='_compute_sales_count', string='Sold', digits='Product Unit of Measure')
|
||||
|
||||
def _compute_sales_count(self):
|
||||
r = {}
|
||||
|
||||
@@ -32,7 +32,7 @@ class ProductTemplate(models.Model):
|
||||
help="Expenses and vendor bills can be re-invoiced to a customer."
|
||||
"With this option, a validated expense can be re-invoice to a customer at its cost or sales price.")
|
||||
visible_expense_policy = fields.Boolean("Re-Invoice Policy visible", compute='_compute_visible_expense_policy')
|
||||
sales_count = fields.Float(compute='_compute_sales_count', string='Sold')
|
||||
sales_count = fields.Float(compute='_compute_sales_count', string='Sold', digits='Product Unit of Measure')
|
||||
visible_qty_configurator = fields.Boolean("Quantity visible in configurator", compute='_compute_visible_qty_configurator')
|
||||
invoice_policy = fields.Selection(
|
||||
[('order', 'Ordered quantities'),
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import wizards
|
||||
|
||||
@@ -20,6 +20,7 @@ by external providers and will automatically generate purchase orders directed t
|
||||
'views/product_views.xml',
|
||||
'views/sale_order_views.xml',
|
||||
'views/purchase_order_views.xml',
|
||||
'wizards/sale_order_cancel_views.xml',
|
||||
],
|
||||
'auto_install': True,
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import sale_order_cancel
|
||||
@@ -0,0 +1,17 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class SaleOrderCancel(models.TransientModel):
|
||||
_inherit = "sale.order.cancel"
|
||||
|
||||
display_purchase_orders_alert = fields.Boolean(
|
||||
string="Purchase Order Alert",
|
||||
compute='_compute_display_purchase_orders_alert'
|
||||
)
|
||||
|
||||
@api.depends('order_id')
|
||||
def _compute_display_purchase_orders_alert(self):
|
||||
for wizard in self:
|
||||
wizard.display_purchase_orders_alert = bool(
|
||||
wizard.order_id.purchase_order_count
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
<odoo>
|
||||
<record id="sale_order_cancel_view_form" model="ir.ui.view">
|
||||
<field name="name">sale.order.cancel.form.inherit</field>
|
||||
<field name="model">sale.order.cancel</field>
|
||||
<field name="inherit_id" ref="sale.sale_order_cancel_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="display_invoice_alert" position="after">
|
||||
<field name="display_purchase_orders_alert" invisible="True" />
|
||||
</field>
|
||||
<span id="display_invoice_alert" position="after">
|
||||
<span id="display_purchase_orders_alert"
|
||||
attrs="{'invisible': [('display_purchase_orders_alert', '=', False)]}">
|
||||
There are active purchase orders linked to this sale order that are not cancelled automatically! <br/>
|
||||
</span>
|
||||
</span>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -16,6 +16,7 @@ class SaleOrder(models.Model):
|
||||
incoterm = fields.Many2one(
|
||||
'account.incoterms', 'Incoterm',
|
||||
help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")
|
||||
incoterm_location = fields.Char(string='Incoterm Location')
|
||||
picking_policy = fields.Selection([
|
||||
('direct', 'As soon as possible'),
|
||||
('one', 'When all products are ready')],
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<xpath expr="//div[@name='expiration_date']" position="after">
|
||||
<div class="col-auto col-3 mw-100 mb-2" t-if="doc.incoterm" groups="sale_stock.group_display_incoterm">
|
||||
<strong>Incoterm:</strong>
|
||||
<p class="m-0" t-field="doc.incoterm.code"/>
|
||||
<p t-if="doc.incoterm_location" t-out="'%s %s' % (doc.incoterm.code, doc.incoterm_location)" class="m-0"/>
|
||||
<p t-else="" t-field="doc.incoterm.code" class="m-0"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<xpath expr="//label[@for='commitment_date']" position="before">
|
||||
<field name="warehouse_id" options="{'no_create': True}" groups="stock.group_stock_multi_warehouses" force_save="1"/>
|
||||
<field name="incoterm" options="{'no_open': True, 'no_create': True}" groups="sale_stock.group_display_incoterm"/>
|
||||
<field name="incoterm_location" groups="sale_stock.group_display_incoterm"/>
|
||||
<field name="picking_policy" required="True"/>
|
||||
</xpath>
|
||||
<xpath expr="//span[@name='expected_date_span']" position="attributes">
|
||||
|
||||
@@ -19,6 +19,7 @@ class PackageType(models.Model):
|
||||
height = fields.Integer('Height', help="Packaging Height")
|
||||
width = fields.Integer('Width', help="Packaging Width")
|
||||
packaging_length = fields.Integer('Length', help="Packaging Length")
|
||||
base_weight = fields.Float(string='Weight', help='Weight of the package type')
|
||||
max_weight = fields.Float('Max Weight', help='Maximum weight shippable in this packaging')
|
||||
barcode = fields.Char('Barcode', copy=False)
|
||||
weight_uom_name = fields.Char(string='Weight unit of measure label', compute='_compute_weight_uom_name', default=_get_default_weight_uom)
|
||||
|
||||
@@ -318,13 +318,13 @@ class Picking(models.Model):
|
||||
location_id = fields.Many2one(
|
||||
'stock.location', "Source Location",
|
||||
default=lambda self: self.env['stock.picking.type'].browse(self._context.get('default_picking_type_id')).default_location_src_id,
|
||||
check_company=True, readonly=True, required=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
check_company=True, required=True,
|
||||
states={'done': [('readonly', True)]})
|
||||
location_dest_id = fields.Many2one(
|
||||
'stock.location', "Destination Location",
|
||||
default=lambda self: self.env['stock.picking.type'].browse(self._context.get('default_picking_type_id')).default_location_dest_id,
|
||||
check_company=True, readonly=True, required=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
check_company=True, required=True,
|
||||
states={'done': [('readonly', True)]})
|
||||
move_ids = fields.One2many('stock.move', 'picking_id', string="Stock Moves", copy=True)
|
||||
move_ids_without_package = fields.One2many(
|
||||
'stock.move', 'picking_id', string="Stock moves not in package", compute='_compute_move_without_package',
|
||||
|
||||
@@ -104,7 +104,8 @@ class StockQuant(models.Model):
|
||||
'Counted Quantity', digits='Product Unit of Measure',
|
||||
help="The product's counted quantity.")
|
||||
inventory_quantity_auto_apply = fields.Float(
|
||||
'Inventoried Quantity', compute='_compute_inventory_quantity_auto_apply',
|
||||
'Inventoried Quantity', digits='Product Unit of Measure',
|
||||
compute='_compute_inventory_quantity_auto_apply',
|
||||
inverse='_set_inventory_quantity', groups='stock.group_stock_manager'
|
||||
)
|
||||
inventory_diff_quantity = fields.Float(
|
||||
@@ -303,6 +304,13 @@ class StockQuant(models.Model):
|
||||
group['inventory_quantity_auto_apply'] = group['quantity']
|
||||
return result
|
||||
|
||||
@api.model
|
||||
def get_import_templates(self):
|
||||
return [{
|
||||
'label': _('Import Template for Inventory Adjustments'),
|
||||
'template': '/stock/static/xlsx/stock_quant.xlsx'
|
||||
}]
|
||||
|
||||
def write(self, vals):
|
||||
""" Override to handle the "inventory mode" and create the inventory move. """
|
||||
allowed_fields = self._get_inventory_fields_write()
|
||||
@@ -877,6 +885,10 @@ class StockQuant(models.Model):
|
||||
name = _('Product Quantity Confirmed')
|
||||
else:
|
||||
name = _('Product Quantity Updated')
|
||||
|
||||
if self.inventory_date:
|
||||
name += _(' [Scheduled on %s]', self.inventory_date)
|
||||
|
||||
return {
|
||||
'name': self.env.context.get('inventory_name') or name,
|
||||
'product_id': self.product_id.id,
|
||||
|
||||
Binary file not shown.
@@ -220,7 +220,7 @@
|
||||
name="%(action_product_replenish)d"
|
||||
context="{'default_product_id': id}"
|
||||
groups="stock.group_stock_user"
|
||||
attrs="{'invisible': [('type', '!=', 'product')]}"/>
|
||||
attrs="{'invisible': [('type', 'not in', ['consu', 'product'])]}"/>
|
||||
</header>
|
||||
<div name="button_box" position="inside">
|
||||
<button string="Putaway Rules" type="object"
|
||||
@@ -249,7 +249,7 @@
|
||||
name="%(action_product_replenish)d"
|
||||
context="{'default_product_id': id}"
|
||||
groups="stock.group_stock_user"
|
||||
attrs="{'invisible': [('type', '!=', 'product')]}"/>
|
||||
attrs="{'invisible': [('type', 'not in', ['consu', 'product'])]}"/>
|
||||
</header>
|
||||
<div name="button_box" position="inside">
|
||||
<field name="show_on_hand_qty_status_button" invisible="1"/>
|
||||
@@ -355,7 +355,7 @@
|
||||
name="%(action_product_replenish)d"
|
||||
context="{'default_product_tmpl_id': id}"
|
||||
groups="stock.group_stock_user"
|
||||
attrs="{'invisible': [('type', '!=', 'product')]}"/>
|
||||
attrs="{'invisible': [('type', 'not in', ['consu', 'product'])]}"/>
|
||||
</header>
|
||||
<div name="button_box" position="inside">
|
||||
<field name="show_on_hand_qty_status_button" invisible="1"/>
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
<field name="height" placeholder="Height"/>
|
||||
<span><field name="length_uom_name" help="Size: Length × Width × Height"/></span>
|
||||
</div>
|
||||
<label for="base_weight"/>
|
||||
<div class="o_row" name="base_weight">
|
||||
<field name="base_weight"/>
|
||||
<span><field name="weight_uom_name"/></span>
|
||||
</div>
|
||||
<label for="max_weight"/>
|
||||
<div class="o_row" name="max_weight">
|
||||
<field name="max_weight"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.float_utils import float_is_zero
|
||||
from odoo.tools.misc import groupby
|
||||
|
||||
@@ -71,6 +71,14 @@ class StockQuant(models.Model):
|
||||
else:
|
||||
super(StockQuant, inventories)._apply_inventory()
|
||||
|
||||
def _get_inventory_move_values(self, qty, location_id, location_dest_id, out=False):
|
||||
res_move = super()._get_inventory_move_values(qty, location_id, location_dest_id, out)
|
||||
if not self.env.context.get('inventory_name'):
|
||||
force_period_date = self.env.context.get('force_period_date', False)
|
||||
if force_period_date:
|
||||
res_move['name'] += _(' [Accounted on %s]', force_period_date)
|
||||
return res_move
|
||||
|
||||
@api.model
|
||||
def _get_inventory_fields_write(self):
|
||||
""" Returns a list of fields user can edit when editing a quant in `inventory_mode`."""
|
||||
|
||||
@@ -24,9 +24,4 @@
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Update existing picking types to include autobatch settings for incoming/outgoing -->
|
||||
<function model="stock.picking.type" name="write">
|
||||
<value model="stock.picking.type" eval="obj().search([('code', 'in', ['incoming', 'outgoing'])]).ids"/>
|
||||
<value eval="{'auto_batch': True, 'batch_group_by_partner': True}"/>
|
||||
</function>
|
||||
</data></odoo>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='picking_id']" position="after">
|
||||
<field name="batch_id" optional="hide"/>
|
||||
<field name="batch_id" optional="show"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user