Compare commits

..

1 Commits

Author SHA1 Message Date
hparfr
955cda6957 has_discount collides with account_cash_discount
rename here because it's easier
2023-04-12 12:15:00 +02:00
180 changed files with 1589 additions and 3587 deletions

View File

@@ -1 +1,2 @@
from . import models from . import account_invoice
from . import account_invoice_report

View File

@@ -4,7 +4,7 @@
{ {
'name': 'Account Invoice Margin', 'name': 'Account Invoice Margin',
'version': '14.0.1.0.0', 'version': '12.0.1.0.0',
'category': 'Invoicing Management', 'category': 'Invoicing Management',
'license': 'AGPL-3', 'license': 'AGPL-3',
'summary': 'Copy standard price on invoice line and compute margins', 'summary': 'Copy standard price on invoice line and compute margins',
@@ -15,10 +15,10 @@ This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>. <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', 'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability', 'website': 'http://www.akretion.com',
'depends': ['account'], 'depends': ['account'],
'data': [ 'data': [
'views/account_move.xml', 'account_invoice_view.xml',
], ],
'installable': True, 'installable': False,
} }

View File

@@ -0,0 +1,152 @@
# Copyright 2015-2019 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
import odoo.addons.decimal_precision as dp
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
standard_price_company_currency = fields.Float(
string='Cost Price in Company Currency', readonly=True,
digits=dp.get_precision('Product Price'),
help="Cost price in company currency in the unit of measure "
"of the invoice line (which may be different from the unit "
"of measure of the product).")
standard_price_invoice_currency = fields.Float(
string='Cost Price in Invoice Currency', readonly=True,
compute='_compute_margin', store=True,
digits=dp.get_precision('Product Price'),
help="Cost price in invoice currency in the unit of measure "
"of the invoice line")
margin_invoice_currency = fields.Monetary(
string='Margin in Invoice Currency', readonly=True, store=True,
compute='_compute_margin', currency_field='currency_id')
margin_company_currency = fields.Monetary(
string='Margin in Company Currency', readonly=True, store=True,
compute='_compute_margin', currency_field='company_currency_id')
margin_rate = fields.Float(
string="Margin Rate", readonly=True, store=True,
compute='_compute_margin',
digits=(16, 2), help="Margin rate in percentage of the sale price")
@api.depends(
'standard_price_company_currency', 'invoice_id.currency_id',
'invoice_id.type', 'invoice_id.company_id',
'invoice_id.date_invoice', 'quantity', 'price_subtotal')
def _compute_margin(self):
for il in self:
standard_price_inv_cur = 0.0
margin_inv_cur = 0.0
margin_comp_cur = 0.0
margin_rate = 0.0
inv = il.invoice_id
if inv and inv.type in ('out_invoice', 'out_refund'):
# it works in _get_current_rate
# even if we set date = False in context
# standard_price_inv_cur is in the UoM of the invoice line
date = inv._get_currency_rate_date() or\
fields.Date.context_today(self)
company = inv.company_id
company_currency = company.currency_id
standard_price_inv_cur =\
company_currency._convert(
il.standard_price_company_currency,
inv.currency_id, company, date)
margin_inv_cur =\
il.price_subtotal - il.quantity * standard_price_inv_cur
margin_comp_cur = inv.currency_id._convert(
margin_inv_cur, company_currency, company, date)
if il.price_subtotal:
margin_rate = 100 * margin_inv_cur / il.price_subtotal
# for a refund, margin should be negative
# but margin rate should stay positive
if inv.type == 'out_refund':
margin_inv_cur *= -1
margin_comp_cur *= -1
il.standard_price_invoice_currency = standard_price_inv_cur
il.margin_invoice_currency = margin_inv_cur
il.margin_company_currency = margin_comp_cur
il.margin_rate = margin_rate
# We want to copy standard_price on invoice line for customer
# invoice/refunds. We can't do that via on_change of product_id,
# because it is not always played when invoice is created from code
# => we inherit write/create
# We write standard_price_company_currency even on supplier invoice/refunds
# because we don't have access to the 'type' of the invoice
@api.model
def create(self, vals):
if vals.get('product_id'):
pp = self.env['product.product'].browse(vals['product_id'])
std_price = pp.standard_price
inv_uom_id = vals.get('uom_id')
if inv_uom_id and inv_uom_id != pp.uom_id.id:
inv_uom = self.env['uom.uom'].browse(inv_uom_id)
std_price = pp.uom_id._compute_price(
std_price, inv_uom)
vals['standard_price_company_currency'] = std_price
return super(AccountInvoiceLine, self).create(vals)
def write(self, vals):
if not vals:
vals = {}
if 'product_id' in vals or 'uom_id' in vals:
for il in self:
if 'product_id' in vals:
if vals.get('product_id'):
pp = self.env['product.product'].browse(
vals['product_id'])
else:
pp = False
else:
pp = il.product_id or False
# uom_id is NOT a required field
if 'uom_id' in vals:
if vals.get('uom_id'):
inv_uom = self.env['uom.uom'].browse(
vals['uom_id'])
else:
inv_uom = False
else:
inv_uom = il.uom_id or False
std_price = 0.0
if pp:
std_price = pp.standard_price
if inv_uom and inv_uom != pp.uom_id:
std_price = pp.uom_id._compute_price(
std_price, inv_uom)
il.write({'standard_price_company_currency': std_price})
return super(AccountInvoiceLine, self).write(vals)
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
margin_invoice_currency = fields.Monetary(
string='Margin in Invoice Currency',
compute='_compute_margin', store=True, readonly=True,
currency_field='currency_id')
margin_company_currency = fields.Monetary(
string='Margin in Company Currency',
compute='_compute_margin', store=True, readonly=True,
currency_field='company_currency_id')
@api.depends(
'type',
'invoice_line_ids.margin_invoice_currency',
'invoice_line_ids.margin_company_currency')
def _compute_margin(self):
res = self.env['account.invoice.line'].read_group(
[('invoice_id', 'in', self.ids)],
['invoice_id', 'margin_invoice_currency',
'margin_company_currency'],
['invoice_id'])
for re in res:
if re['invoice_id']:
inv = self.browse(re['invoice_id'][0])
if inv.type in ('out_invoice', 'out_refund'):
inv.margin_invoice_currency = re['margin_invoice_currency']
inv.margin_company_currency = re['margin_company_currency']

View File

@@ -0,0 +1,60 @@
# Copyright 2018-2019 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class AccountInvoiceReport(models.Model):
_inherit = 'account.invoice.report'
margin = fields.Float(string='Margin', readonly=True)
# why digits=0 ??? Why is it like that in the native "account" module
user_currency_margin = fields.Float(
string="Margin", compute='_compute_user_currency_margin', digits=0)
_depends = {
'account.invoice': [
'account_id', 'amount_total_company_signed',
'commercial_partner_id', 'company_id',
'currency_id', 'date_due', 'date_invoice', 'fiscal_position_id',
'journal_id', 'number', 'partner_bank_id', 'partner_id',
'payment_term_id', 'residual', 'state', 'type', 'user_id',
],
'account.invoice.line': [
'account_id', 'invoice_id', 'price_subtotal', 'product_id',
'quantity', 'uom_id', 'account_analytic_id',
'margin_company_currency',
],
'product.product': ['product_tmpl_id'],
'product.template': ['categ_id'],
'uom.uom': ['category_id', 'factor', 'name', 'uom_type'],
'res.currency.rate': ['currency_id', 'name'],
'res.partner': ['country_id'],
}
@api.depends('currency_id', 'date', 'margin')
def _compute_user_currency_margin(self):
user_currency = self.env.user.company_id.currency_id
currency_rate = self.env['res.currency.rate'].search([
('rate', '=', 1),
'|',
('company_id', '=', self.env.user.company_id.id),
('company_id', '=', False)], limit=1)
base_currency = currency_rate.currency_id
for record in self:
date = record.date or fields.Date.today()
company = record.company_id
record.user_currency_margin = base_currency._convert(
record.margin, user_currency, company, date)
# TODO check for refunds
def _sub_select(self):
select_str = super(AccountInvoiceReport, self)._sub_select()
select_str += ", SUM(ail.margin_company_currency) AS margin"
return select_str
def _select(self):
select_str = super(AccountInvoiceReport, self)._select()
select_str += ", sub.margin AS margin"
return select_str

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2015-2017 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_invoice_line_form" model="ir.ui.view">
<field name="name">margin.account.invoice.line.form</field>
<field name="model">account.invoice.line</field>
<field name="inherit_id" ref="account.view_invoice_line_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='analytic_tag_ids']/.." position="inside">
<field name="standard_price_company_currency"
string="Cost Price in Comp. Cur."
groups="base.group_no_one"/>
<field name="standard_price_invoice_currency"
string="Cost Price in Inv. Cur."
groups="base.group_no_one"/>
<field name="margin_invoice_currency"
string="Margin in Inv. Cur."
groups="base.group_no_one"/>
<field name="margin_company_currency"
string="Margin in Comp. Cur."
groups="base.group_no_one"/>
<label for="margin_rate" groups="base.group_no_one"/>
<div name="margin_rate" groups="base.group_no_one">
<field name="margin_rate" class="oe_inline"/> %
</div>
</xpath>
</field>
</record>
<record id="invoice_form" model="ir.ui.view">
<field name="name">margin.account.invoice.form</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<field name="move_id" position="after">
<field name="margin_invoice_currency"
string="Margin in Inv. Cur." groups="base.group_no_one"/>
<field name="margin_company_currency"
string="Margin in Comp. Cur." groups="base.group_no_one"/>
</field>
</field>
</record>
</odoo>

View File

@@ -1,2 +0,0 @@
from . import account_move
from . import account_invoice_report

View File

@@ -1,36 +0,0 @@
# Copyright 2018-2019 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class AccountInvoiceReport(models.Model):
_inherit = 'account.invoice.report'
margin = fields.Float(string='Margin', readonly=True)
# added margin_company_currency on account.move.line
_depends = {
'account.move': [
'name', 'state', 'move_type', 'partner_id', 'invoice_user_id', 'fiscal_position_id',
'invoice_date', 'invoice_date_due', 'invoice_payment_term_id', 'partner_bank_id',
],
'account.move.line': [
'quantity', 'price_subtotal', 'amount_residual', 'balance', 'amount_currency',
'move_id', 'product_id', 'product_uom_id', 'account_id', 'analytic_account_id',
'journal_id', 'company_id', 'currency_id', 'partner_id',
'margin_company_currency',
],
'product.product': ['product_tmpl_id'],
'product.template': ['categ_id'],
'uom.uom': ['category_id', 'factor', 'name', 'uom_type'],
'res.currency.rate': ['currency_id', 'name'],
'res.partner': ['country_id'],
}
@api.model
def _select(self):
select_str = super()._select()
select_str += ", line.margin_company_currency * currency_table.rate AS margin"
return select_str

View File

@@ -1,155 +0,0 @@
# Copyright 2015-2021 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
standard_price_company_currency = fields.Float(
string='Unit Cost Price in Company Currency', readonly=True,
digits='Product Price',
help="Unit Cost price in company currency in the unit of measure "
"of the invoice line (which may be different from the unit "
"of measure of the product).")
standard_price_invoice_currency = fields.Float(
string='Unit Cost Price in Invoice Currency',
compute='_compute_margin', store=True, digits='Product Price',
help="Unit Cost price in invoice currency in the unit of measure "
"of the invoice line.")
margin_invoice_currency = fields.Monetary(
string='Margin in Invoice Currency', store=True,
compute='_compute_margin', currency_field='currency_id')
margin_company_currency = fields.Monetary(
string='Margin in Company Currency', store=True,
compute='_compute_margin', currency_field='company_currency_id')
margin_rate = fields.Float(
string="Margin Rate", readonly=True, store=True,
compute='_compute_margin',
digits=(16, 2), help="Margin rate in percentage of the sale price")
@api.depends(
'standard_price_company_currency', 'move_id.currency_id',
'move_id.move_type', 'move_id.company_id',
'move_id.invoice_date', 'quantity', 'price_subtotal')
def _compute_margin(self):
for ml in self:
standard_price_inv_cur = 0.0
margin_inv_cur = 0.0
margin_comp_cur = 0.0
margin_rate = 0.0
move = ml.move_id
if move.move_type and move.move_type in ('out_invoice', 'out_refund'):
# it works in _get_current_rate
# even if we set date = False in context
# standard_price_inv_cur is in the UoM of the invoice line
date = move.date or fields.Date.context_today(self)
company = move.company_id
company_currency = company.currency_id
standard_price_inv_cur =\
company_currency._convert(
ml.standard_price_company_currency,
ml.currency_id, company, date)
margin_inv_cur =\
ml.price_subtotal - ml.quantity * standard_price_inv_cur
margin_comp_cur = move.currency_id._convert(
margin_inv_cur, company_currency, company, date)
if ml.price_subtotal:
margin_rate = 100 * margin_inv_cur / ml.price_subtotal
# for a refund, margin should be negative
# but margin rate should stay positive
if move.move_type == 'out_refund':
margin_inv_cur *= -1
margin_comp_cur *= -1
ml.standard_price_invoice_currency = standard_price_inv_cur
ml.margin_invoice_currency = margin_inv_cur
ml.margin_company_currency = margin_comp_cur
ml.margin_rate = margin_rate
# We want to copy standard_price on invoice line for customer
# invoice/refunds. We can't do that via on_change of product_id,
# because it is not always played when invoice is created from code
# => we inherit write/create
# We write standard_price_company_currency even on supplier invoice/refunds
# because we don't have access to the 'type' of the invoice
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('product_id') and not vals.get('display_type'):
pp = self.env['product.product'].browse(vals['product_id'])
std_price = pp.standard_price
inv_uom_id = vals.get('product_uom_id')
if inv_uom_id and inv_uom_id != pp.uom_id.id:
inv_uom = self.env['uom.uom'].browse(inv_uom_id)
std_price = pp.uom_id._compute_price(
std_price, inv_uom)
vals['standard_price_company_currency'] = std_price
return super().create(vals_list)
def write(self, vals):
if not vals:
vals = {}
if 'product_id' in vals or 'product_uom_id' in vals:
for il in self:
if 'product_id' in vals:
if vals.get('product_id'):
pp = self.env['product.product'].browse(
vals['product_id'])
else:
pp = False
else:
pp = il.product_id or False
# uom_id is NOT a required field
if 'product_uom_id' in vals:
if vals.get('product_uom_id'):
inv_uom = self.env['uom.uom'].browse(
vals['product_uom_id'])
else:
inv_uom = False
else:
inv_uom = il.uom_id or False
std_price = 0.0
if pp:
std_price = pp.standard_price
if inv_uom and inv_uom != pp.uom_id:
std_price = pp.uom_id._compute_price(
std_price, inv_uom)
il.write({'standard_price_company_currency': std_price})
return super().write(vals)
class AccountMove(models.Model):
_inherit = 'account.move'
margin_invoice_currency = fields.Monetary(
string='Margin in Invoice Currency',
compute='_compute_margin', store=True,
currency_field='currency_id')
margin_company_currency = fields.Monetary(
string='Margin in Company Currency',
compute='_compute_margin', store=True,
currency_field='company_currency_id')
@api.depends(
'move_type',
'invoice_line_ids.margin_invoice_currency',
'invoice_line_ids.margin_company_currency')
def _compute_margin(self):
rg_res = self.env['account.move.line'].read_group(
[
('move_id', 'in', self.ids),
('display_type', '=', False),
('exclude_from_invoice_tab', '=', False),
('move_id.move_type', 'in', ('out_invoice', 'out_refund')),
],
['move_id', 'margin_invoice_currency:sum', 'margin_company_currency:sum'],
['move_id'])
mapped_data = dict([(x['move_id'][0], {
'margin_invoice_currency': x['margin_invoice_currency'],
'margin_company_currency': x['margin_company_currency'],
}) for x in rg_res])
for move in self:
move.margin_invoice_currency = mapped_data.get(move.id, {}).get('margin_invoice_currency')
move.margin_company_currency = mapped_data.get(move.id, {}).get('margin_company_currency')

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2015-2024 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_move_form" model="ir.ui.view">
<field name="name">margin.account.move.form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<group name="sale_info_group" position="inside">
<field name="margin_invoice_currency"
groups="base.group_no_one"
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<field name="margin_company_currency"
groups="base.group_no_one"
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
</group>
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='price_total']" position="after">
<field name="standard_price_invoice_currency" optional="hide" attrs="{'column_invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<field name="margin_invoice_currency" optional="hide" attrs="{'column_invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<field name="margin_rate" optional="hide" string="Margin Rate (%)" attrs="{'column_invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
</xpath>
<xpath expr="//field[@name='invoice_line_ids']/form//field[@name='price_total']/.." position="inside">
<field name="standard_price_company_currency"
groups="base.group_no_one" attrs="{'invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<field name="standard_price_invoice_currency"
groups="base.group_no_one" attrs="{'invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<field name="margin_invoice_currency"
groups="base.group_no_one" attrs="{'invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<field name="margin_company_currency"
groups="base.group_no_one" attrs="{'invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<label for="margin_rate" groups="base.group_no_one" attrs="{'invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
<div name="margin_rate" groups="base.group_no_one" attrs="{'invisible': [('parent.move_type', 'not in', ('out_invoice', 'out_refund'))]}">
<field name="margin_rate" class="oe_inline"/> %
</div>
</xpath>
</field>
</record>
<record id="view_invoice_tree" model="ir.ui.view">
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_invoice_tree"/>
<field name="arch" type="xml">
<field name="amount_residual_signed" position="after">
<field name="margin_company_currency" optional="hide" sum="1" invisible="context.get('default_move_type') not in ('out_invoice', 'out_refund')" string="Margin"/>
</field>
</field>
</record>
</odoo>

View File

@@ -11,8 +11,7 @@
<field name="inherit_id" ref="account.view_move_form"/> <field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<button name="button_draft" position="before"> <button name="button_draft" position="before">
<button name="prepare_update_wizard" type="object" string="Update Invoice" groups="account.group_account_invoice" attrs="{'invisible': ['|', ('state', '!=', 'posted'), ('move_type', '=', 'entry')]}"/> <button name="prepare_update_wizard" type="object" string="Update Invoice" states="posted" groups="account.group_account_invoice"/>
<button name="prepare_update_wizard" type="object" string="Update Entry" groups="account.group_account_invoice" attrs="{'invisible': ['|', ('state', '!=', 'posted'), ('move_type', '!=', 'entry')]}"/>
</button> </button>
</field> </field>
</record> </record>

View File

@@ -21,7 +21,6 @@ class AccountMoveUpdate(models.TransientModel):
invoice_payment_term_id = fields.Many2one( invoice_payment_term_id = fields.Many2one(
'account.payment.term', string='Payment Term') 'account.payment.term', string='Payment Term')
ref = fields.Char(string='Reference') # field label is customized in the view ref = fields.Char(string='Reference') # field label is customized in the view
invoice_date = fields.Date()
invoice_origin = fields.Char(string='Source Document') invoice_origin = fields.Char(string='Source Document')
partner_bank_id = fields.Many2one( partner_bank_id = fields.Many2one(
'res.partner.bank', string='Bank Account') 'res.partner.bank', string='Bank Account')
@@ -31,7 +30,7 @@ class AccountMoveUpdate(models.TransientModel):
@api.model @api.model
def _simple_fields2update(self): def _simple_fields2update(self):
'''List boolean, date, datetime, char, text fields''' '''List boolean, date, datetime, char, text fields'''
return ['ref', 'invoice_origin', 'invoice_date'] return ['ref', 'invoice_origin']
@api.model @api.model
def _m2o_fields2update(self): def _m2o_fields2update(self):

View File

@@ -15,15 +15,13 @@
<field name="move_type" invisible="1"/> <field name="move_type" invisible="1"/>
<field name="company_id" invisible="1"/> <field name="company_id" invisible="1"/>
<field name="partner_id" invisible="1"/> <field name="partner_id" invisible="1"/>
<field string="Bill Date" attrs="{'invisible': [('move_type', 'not in', ('in_invoice', 'in_refund'))]}" name="invoice_date"/> <field string="Bill Reference" attrs="{'invisible': [('move_type', 'not in', ('in_invoice', 'in_refund'))]}" name="ref"/>
<field string="Supplier Bill Reference" attrs="{'invisible': [('move_type', 'not in', ('in_invoice', 'in_refund'))]}" name="ref"/>
<field string="Customer Reference" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}" name="ref"/> <field string="Customer Reference" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}" name="ref"/>
<field string="Ref" attrs="{'invisible': [('move_type', '!=', 'entry')]}" name="ref"/> <field name="invoice_origin"/>
<field name="invoice_origin" attrs="{'invisible': [('move_type', '=', 'entry')]}"/>
<!-- update of payment term is broken --> <!-- update of payment term is broken -->
<!-- <field name="invoice_payment_term_id" widget="selection"/>--> <!-- <field name="invoice_payment_term_id" widget="selection"/>-->
<field name="partner_bank_id" attrs="{'invisible': [('move_type', '=', 'entry')]}"/> <field name="partner_bank_id"/>
<field name="user_id" options="{'no_open': True, 'no_create': True, 'no_create_edit': True}" attrs="{'invisible': [('move_type', '=', 'entry')]}"/> <field name="user_id" options="{'no_open': True, 'no_create': True, 'no_create_edit': True}"/>
</group> </group>
<group name="lines"> <group name="lines">
<field name="line_ids" nolabel="1" widget="section_and_note_one2many"> <field name="line_ids" nolabel="1" widget="section_and_note_one2many">
@@ -32,8 +30,8 @@
<field name="display_type" invisible="1"/> <field name="display_type" invisible="1"/>
<field name="currency_id" invisible="1"/> <field name="currency_id" invisible="1"/>
<field name="name"/> <field name="name"/>
<field name="quantity" attrs="{'invisible': [('display_type', '!=', False)], 'column_invisible': [('parent.move_type', '=', 'entry')]}"/> <field name="quantity" attrs="{'invisible': [('display_type', '!=', False)]}"/>
<field name="price_subtotal" attrs="{'invisible': [('display_type', '!=', False)], 'column_invisible': [('parent.move_type', '=', 'entry')]}"/> <field name="price_subtotal" attrs="{'invisible': [('display_type', '!=', False)]}"/>
<field name="analytic_account_id" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting"/> <field name="analytic_account_id" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting"/>
<field name="analytic_tag_ids" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_tags" widget="many2many_tags"/> <field name="analytic_tag_ids" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_tags" widget="many2many_tags"/>
</tree> </tree>

View File

@@ -32,7 +32,7 @@ This modules adds the following functions:
* A wizard to mark several invoices as sent at once (forward from v8) * A wizard to mark several invoices as sent at once (forward from v8)
* Default date for Account Move Reversal is now D+1 instead of today * Default date for Account Move Reversal is now D+1 instead of today
* Track more fields on invoice (see details in account.py) * Track more fields on invoice (see details in account.py)
* Add boolean fields `has_discount` and `has_attachment` on invoice * Add boolean fields `has_line_discount` and `has_attachment` on invoice
* Add button "Delete line qty = 0" on supplier invoice * Add button "Delete line qty = 0" on supplier invoice
* Cut name_get() of invoice if too long * Cut name_get() of invoice if too long
* A script for if Odoo screws up invoice attachment filename * A script for if Odoo screws up invoice attachment filename

View File

@@ -30,7 +30,6 @@
'views/product.xml', 'views/product.xml',
'views/res_config_settings.xml', 'views/res_config_settings.xml',
'views/res_company.xml', 'views/res_company.xml',
'views/res_partner.xml',
'views/account_report.xml', 'views/account_report.xml',
'views/account_reconcile_model.xml', 'views/account_reconcile_model.xml',
'wizard/account_invoice_mark_sent_view.xml', 'wizard/account_invoice_mark_sent_view.xml',

View File

@@ -296,11 +296,16 @@ msgstr "Possède une pièce jointe"
#. module: account_usability #. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_bank_statement_line__has_discount #: model:ir.model.fields,field_description:account_usability.field_account_bank_statement_line__has_discount
#: model:ir.model.fields,field_description:account_usability.field_account_move__has_discount
#: model:ir.model.fields,field_description:account_usability.field_account_payment__has_discount #: model:ir.model.fields,field_description:account_usability.field_account_payment__has_discount
msgid "Has Discount" msgid "Has Discount"
msgstr "A une réduction" msgstr "A une réduction"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move__has_line_discount
msgid "Has Line Discount"
msgstr "Contient une réduction"
#. module: account_usability #. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_bank_statement__hide_bank_statement_balance #: model:ir.model.fields,field_description:account_usability.field_account_bank_statement__hide_bank_statement_balance
#: model:ir.model.fields,field_description:account_usability.field_account_journal__hide_bank_statement_balance #: model:ir.model.fields,field_description:account_usability.field_account_journal__hide_bank_statement_balance

View File

@@ -6,7 +6,7 @@ from datetime import timedelta
import logging import logging
from odoo import api, fields, models, _ from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError
from odoo.osv import expression from odoo.osv import expression
from odoo.tools import float_is_zero from odoo.tools import float_is_zero
from odoo.tools.misc import format_date from odoo.tools.misc import format_date
@@ -28,8 +28,8 @@ class AccountMove(models.Model):
fiscal_position_id = fields.Many2one(tracking=True) fiscal_position_id = fields.Many2one(tracking=True)
amount_total = fields.Monetary(tracking=True) amount_total = fields.Monetary(tracking=True)
# for invoice report # for invoice report
has_discount = fields.Boolean( has_line_discount = fields.Boolean(
compute='_compute_has_discount', readonly=True) compute='_compute_has_line_discount', readonly=True)
# has_attachment is useful for those who use attachment to archive # has_attachment is useful for those who use attachment to archive
# supplier invoices. It allows them to find supplier invoices # supplier invoices. It allows them to find supplier invoices
# that don't have any attachment # that don't have any attachment
@@ -40,44 +40,16 @@ class AccountMove(models.Model):
compute="_compute_sales_dates", readonly=True, compute="_compute_sales_dates", readonly=True,
help="This information appear on invoice qweb report " help="This information appear on invoice qweb report "
"(you may use it for your own report)") "(you may use it for your own report)")
# There is a native "blocked" field (bool) on account.move.line
# We want to have that field on invoices to improve usability
# while keeping compatibility with the standard Odoo datamodel
blocked = fields.Boolean(
compute="_compute_blocked",
inverse="_inverse_blocked",
store=True,
string="Dispute",
tracking=True,
)
@api.depends("line_ids", "line_ids.blocked") def _compute_has_line_discount(self):
def _compute_blocked(self):
for move in self:
move.blocked = any(
[
l.blocked
for l in move.line_ids
if l.account_id.internal_type in ("payable", "receivable")
]
)
def _inverse_blocked(self):
for move in self:
for line in move.line_ids.filtered(
lambda l: l.account_id.internal_type in ("payable", "receivable")
):
line.blocked = move.blocked
def _compute_has_discount(self):
prec = self.env['decimal.precision'].precision_get('Discount') prec = self.env['decimal.precision'].precision_get('Discount')
for inv in self: for inv in self:
has_discount = False has_line_discount = False
for line in inv.invoice_line_ids: for line in inv.invoice_line_ids:
if not line.display_type and not float_is_zero(line.discount, precision_digits=prec): if not line.display_type and not float_is_zero(line.discount, precision_digits=prec):
has_discount = True has_line_discount = True
break break
inv.has_discount = has_discount inv.has_line_discount = has_line_discount
def _compute_has_attachment(self): def _compute_has_attachment(self):
iao = self.env['ir.attachment'] iao = self.env['ir.attachment']
@@ -140,16 +112,6 @@ class AccountMove(models.Model):
# self.invalidate_cache() # self.invalidate_cache()
# return res # return res
def _reverse_moves(self, default_values_list=None, cancel=False):
reverse_moves = super()._reverse_moves(
default_values_list=default_values_list, cancel=cancel)
# In the simple scenario 1 invoice -> 1 refund, we add a message in the chatter
# of the invoice and in the chatter of the refund
if len(self) == 1 and len(reverse_moves) == 1:
self.message_post(body=_("A reverse journal entry <a href=# data-oe-model=account.move data-oe-id=%d>%s</a> has been generated.") % (reverse_moves.id, reverse_moves.display_name))
reverse_moves.message_post(body=_("This journal entry has been generated as the reverse of <a href=# data-oe-model=account.move data-oe-id=%d>%s</a>.") % (self.id, self.display_name))
return reverse_moves
def delete_lines_qty_zero(self): def delete_lines_qty_zero(self):
lines = self.env['account.move.line'].search([ lines = self.env['account.move.line'].search([
('display_type', '=', False), ('display_type', '=', False),
@@ -263,40 +225,6 @@ class AccountMove(models.Model):
date = invoice_date date = invoice_date
return date return date
# I don't use account_invoice_supplier_ref_unique because it adds
# a field supplier_invoice_number on account.move instead of using the native field
# cf https://github.com/OCA/account-invoicing/issues/1484
# So I take inspiration from the code of account_invoice_supplier_ref_unique
# but I use the native "ref" field
@api.constrains("ref", "partner_id")
def _check_in_invoice_ref_unique_insensitive(self):
for move in self:
if move.ref and move.is_purchase_document(
include_receipts=True
):
in_invoice_same_ref = self.search(
[
("commercial_partner_id", "=", move.commercial_partner_id.id),
("move_type", "in", ("in_invoice", "in_refund")),
("company_id", "=", move.company_id.id),
("ref", "=ilike", move.ref),
("id", "!=", move.id),
],
limit=1,
)
if in_invoice_same_ref:
raise ValidationError(
_(
"An invoice already exists in Odoo with the same "
"bill reference '%s' for the same supplier '%s': %s."
)
% (
in_invoice_same_ref.ref,
in_invoice_same_ref.partner_id.display_name,
in_invoice_same_ref.display_name,
)
)
class AccountMoveLine(models.Model): class AccountMoveLine(models.Model):
_inherit = 'account.move.line' _inherit = 'account.move.line'

View File

@@ -47,16 +47,6 @@
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='product_id']" position="after"> <xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='product_id']" position="after">
<field name="product_barcode" optional="hide"/> <field name="product_barcode" optional="hide"/>
</xpath> </xpath>
<field name="auto_post" position="before">
<field name="blocked"/>
</field>
<div role="alert" position="after">
<div id="warn_blocked" groups="account.group_account_invoice,account.group_account_readonly"
class="alert alert-warning" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': ['|', ('move_type', 'not in', ('in_invoice', 'in_refund', 'out_invoice', 'out_refund')), ('blocked', '=', False)]}">
This <field name="move_type"/> is marked as <b>disputed</b>.
</div>
</div>
</field> </field>
</record> </record>
@@ -81,8 +71,6 @@
<filter name="sent" string="Sent" domain="[('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund'))]"/> <filter name="sent" string="Sent" domain="[('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund'))]"/>
<separator/> <separator/>
<filter name="no_attachment" string="Missing Attachment" domain="[('has_attachment', '=', False)]"/> <filter name="no_attachment" string="Missing Attachment" domain="[('has_attachment', '=', False)]"/>
<separator/>
<filter name="dispute" string="Dispute" domain="[('blocked', '=', True)]"/>
</filter> </filter>
<filter name="salesperson" position="before"> <filter name="salesperson" position="before">
<filter name="commercial_partner_groupby" string="Commercial Partner" context="{'group_by': 'commercial_partner_id'}"/> <filter name="commercial_partner_groupby" string="Commercial Partner" context="{'group_by': 'commercial_partner_id'}"/>
@@ -93,23 +81,6 @@
</field> </field>
</record> </record>
<record id="view_move_line_form" model="ir.ui.view">
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_form"/>
<field name="arch" type="xml">
<!-- The field 'blocked' is alone in it's block
We don't want to display an empty block, so we put the attrs on the group
The drawback of this is that, if someone added a field in that group,
he won't see the field when internal_type is not payable/receivable -->
<xpath expr="//field[@name='blocked']/.." position="attributes">
<attribute name="attrs">{'invisible': [('account_internal_type', 'not in', ('payable', 'receivable'))]}</attribute>
</xpath>
<field name="company_id" position="after">
<field name="account_internal_type" invisible="1"/>
</field>
</field>
</record>
<record id="view_move_line_tree" model="ir.ui.view"> <record id="view_move_line_tree" model="ir.ui.view">
<field name="model">account.move.line</field> <field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree"/> <field name="inherit_id" ref="account.view_move_line_tree"/>
@@ -155,18 +126,6 @@
</field> </field>
</record> </record>
<record id="view_move_line_pivot" model="ir.ui.view">
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_pivot"/>
<field name="arch" type="xml">
<!-- By default, date is split by month... but if you've been using Odoo for several years,
the pivot table becomes very big by default: so we split by year -->
<field name="date" position="attributes">
<attribute name="interval">year</attribute>
</field>
</field>
</record>
<record id="account.action_move_journal_line" model="ir.actions.act_window"> <record id="account.action_move_journal_line" model="ir.actions.act_window">
<field name="context">{'default_move_type': 'entry', 'view_no_maturity': True}</field> <field name="context">{'default_move_type': 'entry', 'view_no_maturity': True}</field>
<!-- Remove 'search_default_misc_filter': 1 --> <!-- Remove 'search_default_misc_filter': 1 -->

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_partner_property_form" model="ir.ui.view">
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='bank_ids']/tree/field[@name='acc_holder_name']" position="attributes">
<attribute name="invisible">0</attribute>
<attribute name="optional">hide</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -15,7 +15,6 @@ class ResPartnerPhone(models.Model):
_name = 'res.partner.phone' _name = 'res.partner.phone'
_order = 'partner_id, type' _order = 'partner_id, type'
_phone_name_sequence = 8 _phone_name_sequence = 8
_phone_name_fields = ["phone"]
_inherit = ['phone.validation.mixin'] _inherit = ['phone.validation.mixin']
_description = 'Multiple emails and phones for partners' _description = 'Multiple emails and phones for partners'
@@ -74,7 +73,7 @@ class ResPartnerPhone(models.Model):
if self._context.get('callerid'): if self._context.get('callerid'):
name = pphone.partner_id.display_name name = pphone.partner_id.display_name
else: else:
name = '%s (%s)' % (pphone.phone, pphone.partner_id.name) name = u'%s (%s)' % (pphone.phone, pphone.partner_id.name)
else: else:
name = pphone.phone name = pphone.phone
res.append((pphone.id, name)) res.append((pphone.id, name))
@@ -105,7 +104,6 @@ class ResPartnerPhone(models.Model):
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = 'res.partner'
_phone_name_fields = []
# in v10, we are supposed to have in DB E.164 format # in v10, we are supposed to have in DB E.164 format
# with the current implementation, we have: # with the current implementation, we have:
@@ -149,8 +147,6 @@ class ResPartner(models.Model):
if vals.get(partner_field): if vals.get(partner_field):
vals['phone_ids'].append( vals['phone_ids'].append(
(0, 0, {'type': type, partner_phone_field: vals[partner_field]})) (0, 0, {'type': type, partner_phone_field: vals[partner_field]}))
if partner_field in vals:
vals.pop(partner_field)
@api.model @api.model
def create(self, vals): def create(self, vals):
@@ -159,6 +155,7 @@ class ResPartner(models.Model):
self._update_create_vals(vals, '1_email_primary', 'email', 'email') self._update_create_vals(vals, '1_email_primary', 'email', 'email')
self._update_create_vals(vals, '3_phone_primary', 'phone', 'phone') self._update_create_vals(vals, '3_phone_primary', 'phone', 'phone')
self._update_create_vals(vals, '5_mobile_primary', 'mobile', 'phone') self._update_create_vals(vals, '5_mobile_primary', 'mobile', 'phone')
# self._update_create_vals(vals, '7_fax_primary', 'fax', 'phone')
return super().create(vals) return super().create(vals)
def _update_write_vals( def _update_write_vals(
@@ -181,16 +178,16 @@ class ResPartner(models.Model):
else: else:
if pphone: if pphone:
vals['phone_ids'].append((2, pphone.id)) vals['phone_ids'].append((2, pphone.id))
vals.pop(partner_field)
def write(self, vals): def write(self, vals):
if 'phone_ids' not in vals: if 'phone_ids' not in vals:
for rec in self: for rec in self:
cvals = dict(vals, phone_ids=[]) vals['phone_ids'] = []
rec._update_write_vals(cvals, '1_email_primary', 'email', 'email') rec._update_write_vals(vals, '1_email_primary', 'email', 'email')
rec._update_write_vals(cvals, '3_phone_primary', 'phone', 'phone') rec._update_write_vals(vals, '3_phone_primary', 'phone', 'phone')
rec._update_write_vals(cvals, '5_mobile_primary', 'mobile', 'phone') rec._update_write_vals(vals, '5_mobile_primary', 'mobile', 'phone')
super(ResPartner, rec).write(cvals) rec._update_write_vals(vals, '7_fax_primary', 'fax', 'phone')
super(ResPartner, rec).write(vals)
return True return True
else: else:
return super().write(vals) return super().write(vals)

View File

@@ -161,12 +161,6 @@
<field name="name" position="attributes"> <field name="name" position="attributes">
<attribute name="filter_domain">['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute> <attribute name="filter_domain">['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute>
</field> </field>
<field name="email" position="attributes">
<attribute name="filter_domain">[('phone_ids.email', 'ilike', self)]</attribute>
</field>
<field name="phone" position="attributes">
<attribute name="filter_domain">[('phone_ids.phone', 'ilike', self)]</attribute>
</field>
</field> </field>
</record> </record>

View File

@@ -10,7 +10,7 @@
'summary': 'Better usability in base module', 'summary': 'Better usability in base module',
'author': 'Akretion', 'author': 'Akretion',
'website': 'http://www.akretion.com', 'website': 'http://www.akretion.com',
'depends': ['web'], 'depends': ['base'],
'data': [ 'data': [
'security/group.xml', 'security/group.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
@@ -21,7 +21,6 @@
'views/ir_module.xml', 'views/ir_module.xml',
'views/ir_sequence.xml', 'views/ir_sequence.xml',
'views/ir_property.xml', 'views/ir_property.xml',
'views/assets.xml',
], ],
'installable': True, 'installable': True,
} }

View File

@@ -6,8 +6,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 14.0\n" "Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 21:27+0000\n" "POT-Creation-Date: 2021-07-01 10:02+0000\n"
"PO-Revision-Date: 2024-03-26 21:27+0000\n" "PO-Revision-Date: 2021-07-01 10:02+0000\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -15,18 +15,6 @@ msgstr ""
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "%s with a capital of"
msgstr ""
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "APE:"
msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_partner_bank #: model:ir.model,name:base_usability.model_res_partner_bank
msgid "Bank Accounts" msgid "Bank Accounts"
@@ -37,22 +25,11 @@ msgstr ""
msgid "Bank Name" msgid "Bank Name"
msgstr "" msgstr ""
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "Capital:"
msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_company #: model:ir.model,name:base_usability.model_res_company
msgid "Companies" msgid "Companies"
msgstr "" msgstr ""
#. module: base_usability
#: model_terms:ir.ui.view,arch_db:base_usability.ir_property_view_search
msgid "Company"
msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_partner #: model:ir.model,name:base_usability.model_res_partner
msgid "Contact" msgid "Contact"
@@ -71,7 +48,6 @@ msgid "Customer Number:"
msgstr "" msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report__display_name
#: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__display_name #: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__display_name
#: model:ir.model.fields,field_description:base_usability.field_ir_model__display_name #: model:ir.model.fields,field_description:base_usability.field_ir_model__display_name
#: model:ir.model.fields,field_description:base_usability.field_res_company__display_name #: model:ir.model.fields,field_description:base_usability.field_res_company__display_name
@@ -89,24 +65,12 @@ msgstr ""
msgid "E-mail:" msgid "E-mail:"
msgstr "" msgstr ""
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "EORI:"
msgstr ""
#. module: base_usability
#: model_terms:ir.ui.view,arch_db:base_usability.ir_property_view_search
msgid "Field"
msgstr ""
#. module: base_usability #. module: base_usability
#: model_terms:ir.ui.view,arch_db:base_usability.res_country_search #: model_terms:ir.ui.view,arch_db:base_usability.res_country_search
msgid "Group By" msgid "Group By"
msgstr "" msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report__id
#: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__id #: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__id
#: model:ir.model.fields,field_description:base_usability.field_ir_model__id #: model:ir.model.fields,field_description:base_usability.field_ir_model__id
#: model:ir.model.fields,field_description:base_usability.field_res_company__id #: model:ir.model.fields,field_description:base_usability.field_res_company__id
@@ -123,7 +87,6 @@ msgid "Installable"
msgstr "" msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report____last_update
#: model:ir.model.fields,field_description:base_usability.field_ir_mail_server____last_update #: model:ir.model.fields,field_description:base_usability.field_ir_mail_server____last_update
#: model:ir.model.fields,field_description:base_usability.field_ir_model____last_update #: model:ir.model.fields,field_description:base_usability.field_ir_model____last_update
#: model:ir.model.fields,field_description:base_usability.field_res_company____last_update #: model:ir.model.fields,field_description:base_usability.field_res_company____last_update
@@ -176,11 +139,6 @@ msgstr ""
msgid "Partner Tags" msgid "Partner Tags"
msgstr "" msgstr ""
#. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report__print_report_name
msgid "Printed Report Name"
msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_res_partner__ref #: model:ir.model.fields,field_description:base_usability.field_res_partner__ref
#: model:ir.model.fields,field_description:base_usability.field_res_users__ref #: model:ir.model.fields,field_description:base_usability.field_res_users__ref
@@ -188,14 +146,8 @@ msgid "Reference"
msgstr "" msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_ir_actions_report #: model_terms:ir.ui.view,arch_db:base_usability.res_country_search
msgid "Report Action" msgid "Search Countries"
msgstr ""
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "SIRET:"
msgstr "" msgstr ""
#. module: base_usability #. module: base_usability
@@ -217,14 +169,6 @@ msgstr ""
msgid "Tel:" msgid "Tel:"
msgstr "" msgstr ""
#. module: base_usability
#: model:ir.model.fields,help:base_usability.field_ir_actions_report__print_report_name
msgid ""
"This is the filename of the report going to download. Keep empty to not "
"change the report filename. You can use a python expression with the "
"'object' and 'time' variables."
msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_users #: model:ir.model,name:base_usability.model_res_users
msgid "Users" msgid "Users"

View File

@@ -6,27 +6,15 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 14.0\n" "Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 21:27+0000\n" "POT-Creation-Date: 2021-07-01 10:02+0000\n"
"PO-Revision-Date: 2024-03-26 21:27+0000\n" "PO-Revision-Date: 2021-07-01 12:15+0200\n"
"Last-Translator: \n" "Last-Translator: Alexis de Lattre <alexis@via.ecp.fr>\n"
"Language-Team: \n" "Language-Team: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "%s with a capital of"
msgstr "%s au capital de"
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "APE:"
msgstr "APE :"
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_partner_bank #: model:ir.model,name:base_usability.model_res_partner_bank
msgid "Bank Accounts" msgid "Bank Accounts"
@@ -37,22 +25,11 @@ msgstr "Comptes bancaires"
msgid "Bank Name" msgid "Bank Name"
msgstr "Nom de la banque" msgstr "Nom de la banque"
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "Capital:"
msgstr "Capital : "
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_company #: model:ir.model,name:base_usability.model_res_company
msgid "Companies" msgid "Companies"
msgstr "Sociétés" msgstr "Sociétés"
#. module: base_usability
#: model_terms:ir.ui.view,arch_db:base_usability.ir_property_view_search
msgid "Company"
msgstr "Société"
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_partner #: model:ir.model,name:base_usability.model_res_partner
msgid "Contact" msgid "Contact"
@@ -65,13 +42,11 @@ msgstr "Devise"
#. module: base_usability #. module: base_usability
#: code:addons/base_usability/models/res_partner.py:0 #: code:addons/base_usability/models/res_partner.py:0
#: code:addons/base_usability/models/res_partner.py:0
#, python-format #, python-format
msgid "Customer Number:" msgid "Customer Number:"
msgstr "N° client :" msgstr "N° client :"
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report__display_name
#: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__display_name #: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__display_name
#: model:ir.model.fields,field_description:base_usability.field_ir_model__display_name #: model:ir.model.fields,field_description:base_usability.field_ir_model__display_name
#: model:ir.model.fields,field_description:base_usability.field_res_company__display_name #: model:ir.model.fields,field_description:base_usability.field_res_company__display_name
@@ -89,24 +64,12 @@ msgstr "Nom affiché"
msgid "E-mail:" msgid "E-mail:"
msgstr "E-mail :" msgstr "E-mail :"
#. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "EORI:"
msgstr "EORI :"
#. module: base_usability
#: model_terms:ir.ui.view,arch_db:base_usability.ir_property_view_search
msgid "Field"
msgstr "Champ"
#. module: base_usability #. module: base_usability
#: model_terms:ir.ui.view,arch_db:base_usability.res_country_search #: model_terms:ir.ui.view,arch_db:base_usability.res_country_search
msgid "Group By" msgid "Group By"
msgstr "Grouper par" msgstr "Grouper par"
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report__id
#: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__id #: model:ir.model.fields,field_description:base_usability.field_ir_mail_server__id
#: model:ir.model.fields,field_description:base_usability.field_ir_model__id #: model:ir.model.fields,field_description:base_usability.field_ir_model__id
#: model:ir.model.fields,field_description:base_usability.field_res_company__id #: model:ir.model.fields,field_description:base_usability.field_res_company__id
@@ -115,7 +78,7 @@ msgstr "Grouper par"
#: model:ir.model.fields,field_description:base_usability.field_res_partner_category__id #: model:ir.model.fields,field_description:base_usability.field_res_partner_category__id
#: model:ir.model.fields,field_description:base_usability.field_res_users__id #: model:ir.model.fields,field_description:base_usability.field_res_users__id
msgid "ID" msgid "ID"
msgstr "" msgstr "ID"
#. module: base_usability #. module: base_usability
#: model_terms:ir.ui.view,arch_db:base_usability.view_module_filter #: model_terms:ir.ui.view,arch_db:base_usability.view_module_filter
@@ -123,7 +86,6 @@ msgid "Installable"
msgstr "Installable" msgstr "Installable"
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report____last_update
#: model:ir.model.fields,field_description:base_usability.field_ir_mail_server____last_update #: model:ir.model.fields,field_description:base_usability.field_ir_mail_server____last_update
#: model:ir.model.fields,field_description:base_usability.field_ir_model____last_update #: model:ir.model.fields,field_description:base_usability.field_ir_model____last_update
#: model:ir.model.fields,field_description:base_usability.field_res_company____last_update #: model:ir.model.fields,field_description:base_usability.field_res_company____last_update
@@ -174,12 +136,7 @@ msgstr "Personne (utilisé pour cacher des entrées de menu natifs)"
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_partner_category #: model:ir.model,name:base_usability.model_res_partner_category
msgid "Partner Tags" msgid "Partner Tags"
msgstr "Étiquettes contact" msgstr "Étiquettes du partenaire"
#. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_ir_actions_report__print_report_name
msgid "Printed Report Name"
msgstr "Nom du rapport imprimé"
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_res_partner__ref #: model:ir.model.fields,field_description:base_usability.field_res_partner__ref
@@ -188,18 +145,11 @@ msgid "Reference"
msgstr "Référence" msgstr "Référence"
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_ir_actions_report #: model_terms:ir.ui.view,arch_db:base_usability.res_country_search
msgid "Report Action" msgid "Search Countries"
msgstr "" msgstr ""
#. module: base_usability #. module: base_usability
#: code:addons/base_usability/models/res_company.py:0
#, python-format
msgid "SIRET:"
msgstr "SIRET :"
#. module: base_usability
#: code:addons/base_usability/models/res_partner.py:0
#: code:addons/base_usability/models/res_partner.py:0 #: code:addons/base_usability/models/res_partner.py:0
#, python-format #, python-format
msgid "Supplier Number:" msgid "Supplier Number:"
@@ -208,7 +158,7 @@ msgstr "N° fournisseur :"
#. module: base_usability #. module: base_usability
#: model:ir.model.fields,field_description:base_usability.field_res_partner_category__name #: model:ir.model.fields,field_description:base_usability.field_res_partner_category__name
msgid "Tag Name" msgid "Tag Name"
msgstr "Libellé de l'étiquette" msgstr "Nom de l'étiquette"
#. module: base_usability #. module: base_usability
#: code:addons/base_usability/models/res_company.py:0 #: code:addons/base_usability/models/res_company.py:0
@@ -217,14 +167,6 @@ msgstr "Libellé de l'étiquette"
msgid "Tel:" msgid "Tel:"
msgstr "Tél :" msgstr "Tél :"
#. module: base_usability
#: model:ir.model.fields,help:base_usability.field_ir_actions_report__print_report_name
msgid ""
"This is the filename of the report going to download. Keep empty to not "
"change the report filename. You can use a python expression with the "
"'object' and 'time' variables."
msgstr ""
#. module: base_usability #. module: base_usability
#: model:ir.model,name:base_usability.model_res_users #: model:ir.model,name:base_usability.model_res_users
msgid "Users" msgid "Users"

View File

@@ -3,7 +3,6 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models, _ from odoo import api, models, _
from odoo.tools.misc import format_amount
class ResCompany(models.Model): class ResCompany(models.Model):
@@ -40,78 +39,32 @@ class ResCompany(models.Model):
'value': self.phone, 'value': self.phone,
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm # http://www.fileformat.info/info/unicode/char/1f4de/index.htm
'icon': '\U0001F4DE', 'icon': '\U0001F4DE',
'label': _('Tel:'), 'label': _('Tel:')},
},
'email': { 'email': {
'value': self.email, 'value': self.email,
# http://www.fileformat.info/info/unicode/char/2709/index.htm # http://www.fileformat.info/info/unicode/char/2709/index.htm
'icon': '\u2709', 'icon': '\u2709',
'label': _('E-mail:'), 'label': _('E-mail:')},
},
'website': { 'website': {
'value': self.website, 'value': self.website,
'icon': '\U0001f310', 'icon': '\U0001f310',
'label': _('Website:'), 'label': _('Website:')},
},
'vat': { 'vat': {
'value': self.vat, 'value': self.vat,
'label': _('VAT:'), 'label': _('VAT:')},
},
'ape': {
'value': hasattr(self, 'ape') and self.ape or False,
'label': _('APE:'),
},
'siret': {
'value': hasattr(self, 'siret') and self.siret or False,
'label': _('SIRET:'),
},
'siren': {
'value': hasattr(self, 'siren') and self.siren or False,
'label': _('SIREN:'),
},
'eori': {
'value': self._get_eori(),
'label': _('EORI:'),
},
'capital': {
# 'capital_amount' added by base_company_extension
'value': hasattr(self, 'capital_amount') and self.capital_amount and format_amount(self.env, self.capital_amount, self.currency_id) or False,
'label': _('Capital:'),
}
} }
# 'legal_type' added by base_company_extension
if hasattr(self, 'legal_type') and self.legal_type:
options['capital']['label'] = _('%s with a capital of') % self.legal_type
return options return options
def _get_eori(self):
eori = False
if self.partner_id.country_id.code == 'FR' and hasattr(self, 'siret') and self.siret:
# Currently migrating from EORI-SIRET to EORI-SIREN :
# https://www.pwcavocats.com/fr/ealertes/ealertes-france/2023/avril/reforme-numero-eori-siren-siret.html
# But, for the moment, we continue to use EORI-SIRET
eori = f'FR{self.siret}'
return eori
def _report_company_legal_name(self): def _report_company_legal_name(self):
'''Method inherited in the module base_company_extension''' '''Method inherited in the module base_company_extension'''
self.ensure_one() self.ensure_one()
return self.name return self.name
def _report_header_line_details(self):
"""This method is designed to be inherited"""
# I decided not to put email in the default header because only a few very small
# companies have a generic company email address
line_details = [['phone', 'website', 'capital'], ['vat', 'siret', 'eori', 'ape']]
return line_details
# for reports # for reports
def _display_report_header( def _display_report_header(
self, line_details=None, icon=True, line_separator=' - '): self, line_details=[['phone', 'website'], ['vat']],
icon=True, line_separator=' - '):
self.ensure_one() self.ensure_one()
if line_details is None:
line_details = self._report_header_line_details()
res = '' res = ''
address = self.partner_id._display_address(without_company=True) address = self.partner_id._display_address(without_company=True)
address = address.replace('\n', ' - ') address = address.replace('\n', ' - ')

View File

@@ -125,20 +125,6 @@ class ResPartner(models.Model):
'label': _('Supplier Number:'), 'label': _('Supplier Number:'),
}, },
} }
if hasattr(self, 'siren'):
options['siren'] = {
'value': self.siren,
'label': _("SIREN:"),
}
if hasattr(self, 'siret'):
if hasattr(self, 'siren'): # l10n_fr_siret is installed
siret = self.siren and self.nic and self.siret or False
else:
siret = self.siret
options['siret'] = {
'value': siret,
'label': _("SIRET:"),
}
res = [] res = []
for detail in details: for detail in details:
if options.get(detail) and options[detail]['value']: if options.get(detail) and options[detail]['value']:

View File

@@ -1,10 +0,0 @@
.o_form_view {
.o_address_format {
.o_address_state {
margin-right: 0;
}
.o_address_zip {
margin-right: 2%;
}
}
}

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template
id="assets_backend"
name="web_sheet_full_width"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside">
<link
rel="stylesheet"
href="/base_usability/static/src/scss/form_view.scss"
/>
</xpath>
</template>
</odoo>

View File

@@ -20,21 +20,6 @@
<div attrs="{'invisible': [('same_vat_partner_id', '=', False)]}" position="attributes"> <div attrs="{'invisible': [('same_vat_partner_id', '=', False)]}" position="attributes">
<attribute name="class">alert alert-warning</attribute> <attribute name="class">alert alert-warning</attribute>
</div> </div>
<!-- Native order: city / state_id / zip. New order: zip / city / state_id -->
<xpath expr="//div[hasclass('o_address_format')]/field[@name='city']" position="before">
<field name="zip" position="move"/>
</xpath>
</field>
</record>
<record id="res_partner_view_form_private" model="ir.ui.view">
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.res_partner_view_form_private"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='bank_ids']/tree/field[@name='acc_holder_name']" position="attributes">
<attribute name="invisible">0</attribute>
<attribute name="optional">hide</attribute>
</xpath>
</field> </field>
</record> </record>

View File

@@ -16,10 +16,6 @@
<field name="bank_name" position="after"> <field name="bank_name" position="after">
<field name="bank_id"/> <field name="bank_id"/>
</field> </field>
<field name="acc_holder_name" position="attributes">
<attribute name="invisible">0</attribute>
<attribute name="optional">hide</attribute>
</field>
</field> </field>
</record> </record>
</odoo> </odoo>

View File

@@ -1,28 +0,0 @@
diff --git a/addons/web/static/src/js/control_panel/custom_filter_item.js b/addons/web/static/src/js/control_panel/custom_filter_item.js
index 6682b660b78..b2ff23344d9 100644
--- a/addons/web/static/src/js/control_panel/custom_filter_item.js
+++ b/addons/web/static/src/js/control_panel/custom_filter_item.js
@@ -180,6 +180,10 @@ odoo.define('web.CustomFilterItem', function (require) {
[field.name, '>=', domainValue[0]],
[field.name, '<=', domainValue[1]]
);
+ } else if (operator.symbol === 'startswith') {
+ domainArray.push([field.name, '=ilike', domainValue[0] + '%']);
+ } else if (operator.symbol === 'endswith') {
+ domainArray.push([field.name, '=ilike', '%' + domainValue[0]]);
} else {
domainArray.push([field.name, operator.symbol, domainValue[0]]);
}
diff --git a/addons/web/static/src/js/control_panel/search_utils.js b/addons/web/static/src/js/control_panel/search_utils.js
index 8fce5b23ef6..d240d2e1fb2 100644
--- a/addons/web/static/src/js/control_panel/search_utils.js
+++ b/addons/web/static/src/js/control_panel/search_utils.js
@@ -18,6 +18,8 @@ odoo.define('web.searchUtils', function (require) {
char: [
{ symbol: "ilike", description: _lt("contains") },
{ symbol: "not ilike", description: _lt("doesn't contain") },
+ { symbol: "startswith", description: _lt("starts with") },
+ { symbol: "endswith", description: _lt("ends with") },
{ symbol: "=", description: _lt("is equal to") },
{ symbol: "!=", description: _lt("is not equal to") },
{ symbol: "!=", description: _lt("is set"), value: false },

View File

@@ -1 +1,4 @@
# -*- coding: utf-8 -*-
from . import intrastat_product_type from . import intrastat_product_type
from .post_install import set_intrastat_type_on_products

View File

@@ -4,7 +4,7 @@
{ {
'name': 'Intrastat Product Type', 'name': 'Intrastat Product Type',
'version': '14.0.1.0.0', 'version': '12.0.1.0.0',
'category': 'Accounting', 'category': 'Accounting',
'license': 'AGPL-3', 'license': 'AGPL-3',
'summary': 'Adds a special field Intrastat Type on Products', 'summary': 'Adds a special field Intrastat Type on Products',
@@ -19,8 +19,9 @@ This module adds a field *Intrastat Type* on the Product Form with 2 possible op
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>. This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', 'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability', 'website': 'http://www.akretion.com',
'depends': ['intrastat_product', 'l10n_fr_intrastat_service'], 'depends': ['intrastat_product', 'l10n_fr_intrastat_service'],
'data': ['product_view.xml'], 'data': ['product_view.xml'],
'installable': True, 'post_init_hook': 'set_intrastat_type_on_products',
'installable': False,
} }

View File

@@ -1,4 +1,5 @@
# Copyright 2016-2023 Akretion (http://www.akretion.com) # -*- coding: utf-8 -*-
# Copyright 2016-2019 Akretion (http://www.akretion.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
@@ -13,36 +14,50 @@ class ProductTemplate(models.Model):
intrastat_type = fields.Selection([ intrastat_type = fields.Selection([
('product', 'Product'), ('product', 'Product'),
('service', 'Service'), ('service', 'Service'),
], ], string='Intrastat Type', default='product', required=True,
compute='_compute_intrastat_type', readonly=False, store=True,
required=True, string='Intrastat Type',
help="Type of product used for the intrastat declarations. " help="Type of product used for the intrastat declarations. "
"For example, you can configure a product with " "For example, you can configure a product with "
"'Product Type' = 'Consumable' and 'Intrastat Type' = 'Service'.") "'Product Type' = 'Consumable' and 'Intrastat Type' = 'Service'.")
@api.multi
@api.constrains('type', 'intrastat_type') @api.constrains('type', 'intrastat_type')
def check_intrastat_type(self): def check_intrastat_type(self):
for pt in self: for pt in self:
if pt.intrastat_type == 'product' and pt.type == 'service': if pt.intrastat_type == 'product' and pt.type == 'service':
raise ValidationError(_( raise ValidationError(_(
"On the product '%s', you cannot set Product Type to " "On the product %s, you cannot set Product Type to "
"'Service' and Intrastat Type to 'Product'.") "'Service' and Intrastat Type to 'Product'.") % pt.name)
% pt.display_name)
if pt.intrastat_type == 'service' and pt.type == 'product': if pt.intrastat_type == 'service' and pt.type == 'product':
raise ValidationError(_( raise ValidationError(_(
"On the product '%s', you cannot set Intrastat Type to " "On the product %s, you cannot set Intrastat Type to "
"'Service' and Product Type to 'Stockable product' " "'Service' and Product Type to 'Stockable product' "
"(but you can set Product Type to 'Consumable' or " "(but you can set Product Type to 'Consumable' or "
"'Service').") % pt.display_name) "'Service').") % pt.name)
@api.depends('type') @api.onchange('type')
def _compute_intrastat_type(self): def intrastat_type_onchange(self):
for pt in self: if self.type in ('product', 'consu'):
if pt.type in ('product', 'consu'): self.intrastat_type = 'product'
intrastat_type = 'product' elif self.type == 'service':
else: self.intrastat_type = 'service'
intrastat_type = 'service'
pt.intrastat_type = intrastat_type @api.model
def create(self, vals):
if vals.get('type'):
if not vals.get('intrastat_type'):
if vals['type'] in ('product', 'consu'):
vals['intrastat_type'] = 'product'
elif vals['type'] == 'service':
vals['intrastat_type'] = 'service'
elif (
vals.get('intrastat_type') == 'product' and
vals['type'] == 'service'):
# usefull because intrastat_type = 'product' by default and
# wizards in other modules that don't depend on this module
# (e.g. sale_rental) may create a product with only
# {'type': 'service'} and no 'intrastat_type'
vals['intrastat_type'] = 'service'
return super(ProductTemplate, self).create(vals)
class L10nFrIntrastatServiceDeclaration(models.Model): class L10nFrIntrastatServiceDeclaration(models.Model):

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2019 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
def set_intrastat_type_on_products(cr, registry):
cr.execute(
"UPDATE product_template SET intrastat_type='service' "
"WHERE type='service'")
return

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright 2016-2023 Akretion France (http://www.akretion.com/) Copyright 2016-2019 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com> @author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->

View File

@@ -1,10 +1,10 @@
# Copyright (C) 2016-2024 Akretion (http://www.akretion.com) # Copyright (C) 2016-2019 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'MRP Average Cost', 'name': 'MRP Average Cost',
'version': '14.0.1.0.0', 'version': '12.0.1.0.0', # WARNING: we'll probably not port this module to v14, because part of its feature is now provided by the module mrp_account
'category': 'Manufactuing', 'category': 'Manufactuing',
'license': 'AGPL-3', 'license': 'AGPL-3',
'summary': 'Update standard_price upon validation of a manufacturing order', 'summary': 'Update standard_price upon validation of a manufacturing order',
@@ -12,16 +12,16 @@
MRP Average Cost MRP Average Cost
================ ================
I initially developped this module for Odoo 12.0, when the module mrp_account didn't exist, so Odoo didn't support the update of the standard cost of a manufactured product. By default, the official stock module updates the standard_price of a product that has costing_method = 'average' when validating an incoming picking. But the official 'mrp' module doesn't do that when you validate a manufactuging order.
In the mrp_account module, you must use workcenters to take the labor costs into account. This module aims at encoding theorical labor costs on the BOM and using it to compute the cost of the finished product. This module adds this feature : when you validate a manufacturing order of a product that has costing method = 'average', the standard_price of the product will be updated by taking into account the standard_price of each raw material and also a number of work hours defined on the BOM.
With this module, when you validate a manufacturing order of a product that has costing method = 'average', the standard_price of the product will be updated by taking into account the standard_price of each raw material and also a number of work hours defined on the BOM plus the extra cost defined of the BOM. Together with this module, I recommend the use of my module product_usability, available in the same branch, which contains a backport of the model product.price.history from v8 to v7.
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>. This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', 'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability', 'website': 'http://www.akretion.com',
'depends': ['mrp'], 'depends': ['mrp'],
'data': [ 'data': [
'security/mrp_average_cost_security.xml', 'security/mrp_average_cost_security.xml',
@@ -29,5 +29,5 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
'data/mrp_data.xml', 'data/mrp_data.xml',
'views/mrp_view.xml', 'views/mrp_view.xml',
], ],
'installable': True, 'installable': False,
} }

View File

@@ -1,9 +1,11 @@
# Copyright (C) 2016-2024 Akretion (http://www.akretion.com) # Copyright (C) 2016-2019 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.tools import float_compare import odoo.addons.decimal_precision as dp
from odoo.exceptions import UserError
from odoo.tools import float_compare, float_is_zero
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -15,19 +17,23 @@ class MrpBomLabourLine(models.Model):
bom_id = fields.Many2one( bom_id = fields.Many2one(
comodel_name='mrp.bom', comodel_name='mrp.bom',
string='Bill of Material', string='Labour Lines',
ondelete='cascade') ondelete='cascade')
labour_time = fields.Float( labour_time = fields.Float(
string='Labour Time', string='Labour Time',
required=True, required=True,
digits='Labour Hours', digits=dp.get_precision('Labour Hours'),
help="Average labour time for the production of " help="Average labour time for the production of "
"items of the BOM, in hours.") "items of the BOM, in hours.")
labour_cost_profile_id = fields.Many2one( labour_cost_profile_id = fields.Many2one(
comodel_name='labour.cost.profile', comodel_name='labour.cost.profile',
string='Labour Cost Profile', string='Labour Cost Profile',
required=True) required=True)
note = fields.Text()
note = fields.Text(
string='Note')
_sql_constraints = [( _sql_constraints = [(
'labour_time_positive', 'labour_time_positive',
@@ -38,26 +44,6 @@ class MrpBomLabourLine(models.Model):
class MrpBom(models.Model): class MrpBom(models.Model):
_inherit = 'mrp.bom' _inherit = 'mrp.bom'
labour_line_ids = fields.One2many(
'mrp.bom.labour.line', 'bom_id', string='Labour Lines')
total_labour_cost = fields.Float(
compute='_compute_total_labour_cost', digits='Product Price', store=True)
extra_cost = fields.Float(
tracking=True, digits='Product Price',
help="Extra cost for the production of the quantity of "
"items of the BOM, in company currency. "
"You can use this field to enter the cost of the consumables "
"that are used to produce the product but are not listed in "
"the BOM")
total_components_cost = fields.Float(
compute='_compute_total_cost', digits='Product Price')
total_cost = fields.Float(
compute='_compute_total_cost', digits='Product Price',
help="Total cost for the quantity and unit of measure of the bill of material. "
"Total Cost = Total Components Cost + Total Labour Cost + Extra Cost")
company_currency_id = fields.Many2one(
related='company_id.currency_id', string='Company Currency')
@api.depends( @api.depends(
'labour_line_ids.labour_time', 'labour_line_ids.labour_time',
'labour_line_ids.labour_cost_profile_id.hour_cost') 'labour_line_ids.labour_cost_profile_id.hour_cost')
@@ -84,77 +70,107 @@ class MrpBom(models.Model):
bom.total_components_cost = comp_cost bom.total_components_cost = comp_cost
bom.total_cost = total_cost bom.total_cost = total_cost
labour_line_ids = fields.One2many(
'mrp.bom.labour.line', 'bom_id', string='Labour Lines')
total_labour_cost = fields.Float(
compute='_compute_total_labour_cost', readonly=True,
digits=dp.get_precision('Product Price'),
string="Total Labour Cost", store=True)
extra_cost = fields.Float(
string='Extra Cost', track_visibility='onchange',
digits=dp.get_precision('Product Price'),
help="Extra cost for the production of the quantity of "
"items of the BOM, in company currency. "
"You can use this field to enter the cost of the consumables "
"that are used to produce the product but are not listed in "
"the BOM")
total_components_cost = fields.Float(
compute='_compute_total_cost', readonly=True,
digits=dp.get_precision('Product Price'),
string='Total Components Cost')
total_cost = fields.Float(
compute='_compute_total_cost', readonly=True,
string='Total Cost',
digits=dp.get_precision('Product Price'),
help="Total Cost = Total Components Cost + "
"Total Labour Cost + Extra Cost")
company_currency_id = fields.Many2one(
related='company_id.currency_id', string='Company Currency')
@api.model @api.model
def _phantom_update_product_standard_price(self): def _phantom_update_product_standard_price(self):
logger.info('Start to auto-update cost price from phantom boms') logger.info('Start to auto-update cost price from phantom bom')
boms = self.search([('type', '=', 'phantom')]) boms = self.search([('type', '=', 'phantom')])
boms.manual_update_product_standard_price() boms.with_context(
logger.info('End of the auto-update cost price from phantom boms') product_price_history_origin='Automatic update of Phantom BOMs')\
.manual_update_product_standard_price()
logger.info('End of the auto-update cost price from phantom bom')
return True
def manual_update_product_standard_price(self): def manual_update_product_standard_price(self):
prec = self.env['decimal.precision'].precision_get( if 'product_price_history_origin' not in self._context:
self = self.with_context(
product_price_history_origin='Manual update from BOM')
precision = self.env['decimal.precision'].precision_get(
'Product Price') 'Product Price')
for bom in self: for bom in self:
if bom.product_id: wproduct = bom.product_id
products = bom.product_id if not wproduct:
else: wproduct = bom.product_tmpl_id
products = bom.product_tmpl_id.product_variant_ids if float_compare(
for product in products: wproduct.standard_price, bom.total_cost,
standard_price = product._compute_bom_price(bom) precision_digits=precision):
if float_compare(product.standard_price, standard_price, precision_digits=prec): wproduct.with_context().write(
product.write({'standard_price': standard_price}) {'standard_price': bom.total_cost})
logger.info( logger.info(
'Cost price updated to %s on product %s', 'Cost price updated to %s on product %s',
standard_price, product.display_name) bom.total_cost, wproduct.display_name)
return True
class MrpBomLine(models.Model): class MrpBomLine(models.Model):
_inherit = 'mrp.bom.line' _inherit = 'mrp.bom.line'
standard_price = fields.Float(related='product_id.standard_price') standard_price = fields.Float(
related='product_id.standard_price',
readonly=True,
class ProductProduct(models.Model): string='Standard Price')
_inherit = 'product.product'
def _compute_bom_price(self, bom, boms_to_recompute=False):
# Native method of mrp_account
# WARNING dirty hack ; I hope it doesn't break too many things
self.ensure_one()
bom_cost_per_unit_in_product_uom = 0
qty_product_uom = bom.product_uom_id._compute_quantity(bom.product_qty, self.uom_id)
if qty_product_uom:
bom_cost_per_unit_in_product_uom = bom.total_cost / qty_product_uom
return bom_cost_per_unit_in_product_uom
class LabourCostProfile(models.Model): class LabourCostProfile(models.Model):
_name = 'labour.cost.profile' _name = 'labour.cost.profile'
_inherit = ['mail.thread', 'mail.activity.mixin'] _inherit = ['mail.thread']
_description = 'Labour Cost Profile' _description = 'Labour Cost Profile'
name = fields.Char( name = fields.Char(
string='Name',
required=True, required=True,
tracking=True) track_visibility='onchange')
hour_cost = fields.Float( hour_cost = fields.Float(
string='Cost per Hour', string='Cost per Hour',
required=True, required=True,
digits='Product Price', digits=dp.get_precision('Product Price'),
tracking=True, track_visibility='onchange',
help="Labour cost per hour per person in company currency") help="Labour cost per hour per person in company currency")
company_id = fields.Many2one( company_id = fields.Many2one(
comodel_name='res.company', required=True, comodel_name='res.company',
default=lambda self: self.env.company) string='Company',
required=True,
default=lambda self: self.env['res.company']._company_default_get())
company_currency_id = fields.Many2one( company_currency_id = fields.Many2one(
related='company_id.currency_id', store=True, string='Company Currency') related='company_id.currency_id',
readonly=True,
store=True,
string='Company Currency')
@api.depends('name', 'hour_cost', 'company_currency_id.symbol') @api.depends('name', 'hour_cost', 'company_currency_id.symbol')
def name_get(self): def name_get(self):
res = [] res = []
for record in self: for record in self:
res.append((record.id, '%s (%s %s)' % ( res.append((record.id, u'%s (%s %s)' % (
record.name, record.hour_cost, record.name, record.hour_cost,
record.company_currency_id.symbol))) record.company_currency_id.symbol)))
return res return res
@@ -163,25 +179,93 @@ class LabourCostProfile(models.Model):
class MrpProduction(models.Model): class MrpProduction(models.Model):
_inherit = 'mrp.production' _inherit = 'mrp.production'
unit_cost = fields.Float(
string='Unit Cost', readonly=True,
digits=dp.get_precision('Product Price'),
help="This cost per unit in the unit of measure of the product "
"in company currency takes into account "
"the cost of the raw materials and the labour cost defined on"
"the BOM.")
company_currency_id = fields.Many2one( company_currency_id = fields.Many2one(
related='company_id.currency_id', string='Company Currency') related='company_id.currency_id', readonly=True,
# extra_cost is per unit in the UoM of the mrp.production (product_uom_id) string='Company Currency')
extra_cost = fields.Float(
compute='_compute_extra_cost', store=True, readonly=False,
help="For a regular production order, it takes into account the labor cost "
"and the extra cost defined on the bill of material.")
# Strategy for v14 : we write labor costs and bom's extra cost on the native field extra_cost def compute_order_unit_cost(self):
# of mrp.production => it is automatically added by the code of mrp_account self.ensure_one()
mo_total_price = 0.0 # In the UoM of the M0
labor_cost_per_unit = 0.0 # In the UoM of the product
extra_cost_per_unit = 0.0 # In the UoM of the product
subcontract_cost_per_unit = 0.0
# I read the raw materials MO, not on BOM, in order to make
# it work with the "dynamic" BOMs (few raw material are auto-added
# on the fly on MO)
prec = self.env['decimal.precision'].precision_get(
'Product Unit of Measure')
for raw_smove in self.move_raw_ids:
# I don't filter on state, in order to make it work with
# partial productions
# For partial productions, mo.product_qty is not updated
# so we compute with fully qty and we compute with all raw
# materials (consumed or not), so it gives a good price
# per unit at the end
raw_price = raw_smove.product_id.standard_price
raw_material_cost = raw_price * raw_smove.product_qty
logger.info(
'MO %s product %s: raw_material_cost=%s',
self.name, raw_smove.product_id.display_name,
raw_material_cost)
mo_total_price += raw_material_cost
if self.bom_id:
bom = self.bom_id
# if not bom.total_labour_cost:
# raise orm.except_orm(
# _('Error:'),
# _("Total Labor Cost is 0 on bill of material '%s'.")
# % bom.name)
if float_is_zero(bom.product_qty, precision_digits=prec):
raise UserError(_(
"Missing Product Quantity on bill of material '%s'.")
% bom.display_name)
bom_qty_product_uom = bom.product_uom_id._compute_quantity(
bom.product_qty, bom.product_tmpl_id.uom_id)
assert bom_qty_product_uom > 0, 'BoM qty should be positive'
labor_cost_per_unit = bom.total_labour_cost / bom_qty_product_uom
extra_cost_per_unit = bom.extra_cost / bom_qty_product_uom
if bom.type == 'subcontract':
one_finished_move = self.env['stock.move'].search([
('production_id', '=', self.id),
('product_id', '=', self.product_id.id),
('move_dest_ids', '!=', False)], limit=1)
if one_finished_move:
subcontract_cost_per_unit = one_finished_move.move_dest_ids[0].price_unit
# mo_standard_price and labor_cost_per_unit are
# in the UoM of the product (not of the MO/BOM)
mo_qty_product_uom = self.product_uom_id._compute_quantity(
self.product_qty, self.product_id.uom_id)
assert mo_qty_product_uom > 0, 'MO qty should be positive'
mo_standard_price = mo_total_price / mo_qty_product_uom
logger.info(
'MO %s: labor_cost_per_unit=%s extra_cost_per_unit=%s '
'subcontract_cost_per_unit=%s',
self.name, labor_cost_per_unit, extra_cost_per_unit,
subcontract_cost_per_unit)
mo_standard_price += labor_cost_per_unit
mo_standard_price += extra_cost_per_unit
mo_standard_price += subcontract_cost_per_unit
return mo_standard_price
@api.depends('bom_id', 'product_id') def post_inventory(self):
def _compute_extra_cost(self): '''This is the method where _action_done() is called on finished move
for prod in self: So we write on 'price_unit' of the finished move and THEN we call
bom = prod.bom_id super() which will call _action_done() which itself calls
if bom and bom.type == 'normal': product_price_update_before_done()'''
extra_cost_bom_qty_uom = bom.extra_cost + bom.total_labour_cost for order in self:
extra_cost_per_unit_in_prod_uom = 0 if order.product_id.cost_method == 'average':
qty_prod_uom = bom.product_uom_id._compute_quantity(bom.product_qty, prod.product_uom_id) unit_cost = order.compute_order_unit_cost()
if qty_prod_uom: order.write({'unit_cost': unit_cost})
extra_cost_per_unit_in_prod_uom = extra_cost_bom_qty_uom / qty_prod_uom logger.info('MO %s: unit_cost=%s', order.name, unit_cost)
prod.extra_cost = extra_cost_per_unit_in_prod_uom order.move_finished_ids.filtered(
lambda x: x.product_id == order.product_id).write({
'price_unit': unit_cost})
return super(MrpProduction, self).post_inventory()

View File

@@ -4,7 +4,7 @@
<record id="labour_cost_profile_rule" model="ir.rule"> <record id="labour_cost_profile_rule" model="ir.rule">
<field name="name">Labour Cost Profile multi-company</field> <field name="name">Labour Cost Profile multi-company</field>
<field name="model_id" ref="model_labour_cost_profile"/> <field name="model_id" ref="model_labour_cost_profile"/>
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field> <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record> </record>
</odoo> </odoo>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright (C) 2016-2024 Akretion (http://www.akretion.com/) Copyright (C) 2016-2019 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com> @author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
@@ -13,25 +13,23 @@
<field name="model">mrp.bom</field> <field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_form_view"/> <field name="inherit_id" ref="mrp.mrp_bom_form_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page[@name='miscellaneous']/group" position="inside"> <field name="picking_type_id" position="after">
<group name="costs"> <field name="total_components_cost" widget="monetary"
<field name="total_components_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
options="{'currency_field': 'company_currency_id'}"/> <field name="total_labour_cost" widget="monetary"
<field name="total_labour_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
options="{'currency_field': 'company_currency_id'}"/> <field name="extra_cost" widget="monetary"
<field name="extra_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
options="{'currency_field': 'company_currency_id'}"/> <label for="total_cost"/>
<label for="total_cost"/> <div>
<div> <field name="total_cost" widget="monetary"
<field name="total_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}"
options="{'currency_field': 'company_currency_id'}" class="oe_inline"/>
class="oe_inline"/> <button type="object" name="manual_update_product_standard_price"
<button type="object" name="manual_update_product_standard_price" string="Update Cost Price of Product" class="oe_link"/>
string="Update Cost Price of Product" class="oe_link"/> </div>
</div> <field name="company_currency_id" invisible="1"/>
<field name="company_currency_id" invisible="1"/> </field>
</group>
</xpath>
<notebook position="inside"> <notebook position="inside">
<page string="Labour" name="labour_lines"> <page string="Labour" name="labour_lines">
<group name="labour_lines_grp"> <group name="labour_lines_grp">
@@ -119,10 +117,10 @@
<field name="inherit_id" ref="mrp.mrp_production_form_view"/> <field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="model">mrp.production</field> <field name="model">mrp.production</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page[@name='miscellaneous']//field[@name='origin']/.." position="inside"> <field name="availability" position="after">
<field name="extra_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}"/> <field name="unit_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}" attrs="{'invisible': [('state', '!=', 'done')]}"/>
<field name="company_currency_id" invisible="1"/> <field name="company_currency_id" invisible="1"/>
</xpath> </field>
</field> </field>
</record> </record>

View File

@@ -4,7 +4,7 @@
{ {
'name': 'MRP No Product Template Menu', 'name': 'MRP No Product Template Menu',
'version': '14.0.1.0.0', 'version': '12.0.1.0.0',
'category': 'Manufacturing', 'category': 'Manufacturing',
'license': 'AGPL-3', 'license': 'AGPL-3',
'summary': "Replace product.template menu entries by product.product menu", 'summary': "Replace product.template menu entries by product.product menu",
@@ -22,9 +22,9 @@ This module has been written by Alexis de Lattre
from Akretion <alexis.delattre@akretion.com>. from Akretion <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', 'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability', 'website': 'http://www.akretion.com',
'depends': ['mrp', 'sale_purchase_no_product_template_menu'], 'depends': ['mrp', 'sale_purchase_no_product_template_menu'],
'auto_install': True, 'auto_install': True,
'data': ['mrp_view.xml'], 'data': ['mrp_view.xml'],
'installable': True, 'installable': False,
} }

View File

@@ -1,18 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright 2016-2024 Akretion France (http://www.akretion.com/) Copyright 2016-2019 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="mrp.menu_mrp_product_form" model="ir.ui.menu"> <record id="product_product_action_mrp" model="ir.actions.act_window">
<field name="action" ref="product.product_normal_action"/> <field name="name">Products</field>
<field name="res_model">product.product</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context">{'search_default_consumable': 1, 'default_type': 'product'}</field>
</record>
<record id="mrp.menu_mrp_product_form" model="ir.ui.menu">
<field name="action" ref="product_product_action_mrp"/>
</record> </record>
<!-- we don't care about:
"search_default_consumable": 1 => not useful
"default_type": 'product' : stock_usability make it the default... no need to bother
-->
</odoo> </odoo>

View File

@@ -1,18 +0,0 @@
# Copyright 2023 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'MRP Subcontracting Usability',
'version': '14.0.1.0.0',
'category': 'Manufacturing',
'license': 'AGPL-3',
'summary': 'Usability improvements on mrp_subcontracting',
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['mrp_subcontracting'],
'data': [
'views/mrp_bom.xml',
],
'installable': True,
}

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_mrp_bom_filter" model="ir.ui.view">
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.view_mrp_bom_filter"/>
<field name="arch" type="xml">
<filter name="phantom" position="after">
<filter name="subcontract" domain="[('type', '=', 'subcontract')]" string="Subcontracting"/>
</filter>
</field>
</record>
</odoo>

View File

@@ -14,7 +14,6 @@
'data': [ 'data': [
'views/mrp_production.xml', 'views/mrp_production.xml',
'views/product_template.xml', 'views/product_template.xml',
'views/stock_move_line.xml',
# 'report/mrp_report.xml' # TODO # 'report/mrp_report.xml' # TODO
], ],
'installable': True, 'installable': True,

View File

@@ -3,23 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models from odoo import api, models
class MrpProduction(models.Model): class MrpProduction(models.Model):
_inherit = 'mrp.production' _inherit = 'mrp.production'
# Allow to change the destination location until 'Mark as done'.
# Native behavior: it is only possible to change it in draft state.
location_dest_id = fields.Many2one(states={
'draft': [('readonly', False)], # native
'confirmed': [('readonly', False)], # added
'progress': [('readonly', False)], # added
'to_close': [('readonly', False)], # added
}, tracking=True)
# Add field product_categ_id for reporting only
product_categ_id = fields.Many2one(related='product_id.categ_id', store=True)
# Method used by the report, inherited in this module # Method used by the report, inherited in this module
@api.model @api.model
def get_stock_move_sold_out_report(self, move): def get_stock_move_sold_out_report(self, move):

View File

@@ -16,9 +16,8 @@
<xpath expr="//page[@name='miscellaneous']/group/group/field[@name='location_src_id']" position="replace"/> <xpath expr="//page[@name='miscellaneous']/group/group/field[@name='location_src_id']" position="replace"/>
<xpath expr="//page[@name='miscellaneous']/group/group/field[@name='location_dest_id']" position="replace"/> <xpath expr="//page[@name='miscellaneous']/group/group/field[@name='location_dest_id']" position="replace"/>
<field name="bom_id" position="after"> <field name="bom_id" position="after">
<!-- no need to set readonly via attrs, the readonly status is in the field definition --> <field name="location_src_id" groups="stock.group_stock_multi_locations" options="{'no_create': True}" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="location_src_id" groups="stock.group_stock_multi_locations" options="{'no_create': True}" /> <field name="location_dest_id" groups="stock.group_stock_multi_locations" options="{'no_create': True}" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations" options="{'no_create': True}"/>
</field> </field>
<xpath expr="//page[@name='miscellaneous']/group/group/field[@name='date_deadline']" position="after"> <xpath expr="//page[@name='miscellaneous']/group/group/field[@name='date_deadline']" position="after">
<field name="date_start"/> <field name="date_start"/>
@@ -41,20 +40,4 @@
</field> </field>
</record> </record>
<!-- Menu Manufacturing > Reporting > Manufacturing orders -->
<record id="mrp.mrp_production_report" model="ir.actions.act_window">
<!-- Change order: pivot first instead of graph -->
<field name="view_mode">pivot,graph,form</field>
</record>
<record id="view_production_pivot" model="ir.ui.view">
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.view_production_pivot"/>
<field name="arch" type="xml">
<pivot position="inside">
<field name="product_uom_qty" type="measure"/>
</pivot>
</field>
</record>
</odoo> </odoo>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_move_line_form" model="ir.ui.view">
<field name="name">mrp_usability.stock.move.line.form</field>
<field name="model">stock.move.line</field>
<field name="inherit_id" ref="stock.view_move_line_form" />
<field name="arch" type="xml">
<field name="reference" position="before">
<field name="production_id" attrs="{'invisible': [('production_id', '=', False)]}"/>
</field>
</field>
</record>
</odoo>

View File

@@ -31,11 +31,7 @@ Akretion:
"views/report_pos_order.xml", "views/report_pos_order.xml",
"views/pos_category.xml", "views/pos_category.xml",
"views/pos_session.xml", "views/pos_session.xml",
"views/pos_order.xml",
"views/pos_config.xml",
"views/product.xml", "views/product.xml",
"views/pos_payment_method.xml",
"views/stock_warehouse.xml",
], ],
"installable": True, "installable": True,
} }

View File

@@ -1,4 +1,3 @@
from . import product from . import product
from . import pos_category from . import pos_category
from . import pos_config
from . import pos_payment_method from . import pos_payment_method

View File

@@ -1,12 +0,0 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class PosConfig(models.Model):
_inherit = 'pos.config'
_order = 'sequence, id'
sequence = fields.Integer(default=10)

View File

@@ -8,7 +8,5 @@ from odoo import fields, models
class PosPaymentMethod(models.Model): class PosPaymentMethod(models.Model):
_inherit = 'pos.payment.method' _inherit = 'pos.payment.method'
_check_company_auto = True _check_company_auto = True
_order = 'sequence, id'
cash_journal_id = fields.Many2one(check_company=True) cash_journal_id = fields.Many2one(check_company=True)
sequence = fields.Integer(default=10)

View File

@@ -1,35 +0,0 @@
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
index 55aa635aa10..d91ea933a71 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
@@ -8,6 +8,7 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
const { useListener } = require('web.custom_hooks');
const Registries = require('point_of_sale.Registries');
const { onChangeOrder } = require('point_of_sale.custom_hooks');
+ const utils = require('web.utils');
class PaymentScreen extends PosComponent {
constructor() {
@@ -20,6 +21,7 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
useListener('send-payment-cancel', this._sendPaymentCancel);
useListener('send-payment-reverse', this._sendPaymentReverse);
useListener('send-force-done', this._sendForceDone);
+ useListener('validate-order', () => this.validateOrder(false));
this.lockedValidateOrder = useAsyncLockedMethod(this.validateOrder);
NumberBuffer.use(this._getNumberBufferConfig);
onChangeOrder(this._onPrevOrder, this._onNewOrder);
@@ -333,6 +335,14 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
if (isPaymentSuccessful) {
line.set_payment_status('done');
line.can_be_reversed = payment_terminal.supports_reversals;
+ // Automatically validate the order if, after an electronic payment,
+ // the current order is fully paid (BACKPORT FROM v16)
+ if (
+ this.currentOrder.is_paid() &&
+ utils.float_is_zero(this.currentOrder.get_due(), this.env.pos.currency.decimals)
+ ) {
+ this.trigger('validate-order');
+ }
} else {
line.set_payment_status('retry');
}

View File

@@ -1,22 +0,0 @@
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 86a7b44bcfb..06b46ba5645 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -514,16 +514,7 @@ exports.PosModel = Backbone.Model.extend({
fields: ['name', 'is_cash_count', 'use_payment_terminal'],
domain: function(self){return ['|',['active', '=', false], ['active', '=', true]]; },
loaded: function(self, payment_methods) {
- self.payment_methods = payment_methods.sort(function(a,b){
- // prefer cash payment_method to be first in the list
- if (a.is_cash_count && !b.is_cash_count) {
- return -1;
- } else if (!a.is_cash_count && b.is_cash_count) {
- return 1;
- } else {
- return a.id - b.id;
- }
- });
+ self.payment_methods = payment_methods;
self.payment_methods_by_id = {};
_.each(self.payment_methods, function(payment_method) {
self.payment_methods_by_id[payment_method.id] = payment_method;

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_pos_config_tree" model="ir.ui.view">
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.view_pos_config_tree"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="sequence" widget="handle"/>
</field>
</field>
</record>
</odoo>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_pos_pos_form" model="ir.ui.view">
<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//field[@name='full_product_name']" position="before">
<field name="product_id"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="pos_payment_method_view_tree" model="ir.ui.view">
<field name="model">pos.payment.method</field>
<field name="inherit_id" ref="point_of_sale.pos_payment_method_view_tree"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="sequence" widget="handle"/>
</field>
</field>
</record>
</odoo>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_warehouse" model="ir.ui.view">
<field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse"/>
<field name="arch" type="xml">
<field name="out_type_id" position="after">
<field name="pos_type_id"/>
</field>
</field>
</record>
</odoo>

View File

@@ -1,50 +1,20 @@
# Translation of Odoo Server. # Translation of Odoo Server.
# This file contains the translation of the following modules: # This file contains the translation of the following modules:
# * product_category_tax # * product_category_tax
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 14.0\n" "Project-Id-Version: Odoo Server 8.0-20150727\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-16 20:34+0000\n" "POT-Creation-Date: 2015-12-04 17:16+0000\n"
"PO-Revision-Date: 2023-10-16 20:34+0000\n" "PO-Revision-Date: 2015-12-04 17:16+0000\n"
"Last-Translator: Alexis de Lattre <alexis.delattre@akretion.com>\n" "Last-Translator: <>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_categ_tax_mixin
msgid "Common code for taxes on product categories"
msgstr "Code commun pour les taxes sur les catégories d'articles"
#. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_categ_tax_mixin__display_name
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__display_name
#: model:ir.model.fields,field_description:product_category_tax.field_product_product__display_name
#: model:ir.model.fields,field_description:product_category_tax.field_product_template__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_categ_tax_mixin__id
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__id
#: model:ir.model.fields,field_description:product_category_tax.field_product_product__id
#: model:ir.model.fields,field_description:product_category_tax.field_product_template__id
msgid "ID"
msgstr "ID"
#. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_categ_tax_mixin____last_update
#: model:ir.model.fields,field_description:product_category_tax.field_product_category____last_update
#: model:ir.model.fields,field_description:product_category_tax.field_product_product____last_update
#: model:ir.model.fields,field_description:product_category_tax.field_product_template____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: product_category_tax #. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_product #: model:ir.model,name:product_category_tax.model_product_product
msgid "Product" msgid "Product"
@@ -53,7 +23,7 @@ msgstr "Article"
#. module: product_category_tax #. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_category #: model:ir.model,name:product_category_tax.model_product_category
msgid "Product Category" msgid "Product Category"
msgstr "Catégorie d'article" msgstr "Catégorie d'articles"
#. module: product_category_tax #. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_template #: model:ir.model,name:product_category_tax.model_product_template
@@ -61,31 +31,23 @@ msgid "Product Template"
msgstr "Modèle d'article" msgstr "Modèle d'article"
#. module: product_category_tax #. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__purchase_tax_ids #: field:product.category,purchase_tax_ids:0
msgid "Purchase Taxes" msgid "Purchase Taxes"
msgstr "Taxes à l'achat" msgstr "Taxes fournisseurs"
#. module: product_category_tax #. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__sale_tax_ids #: field:product.category,sale_tax_ids:0
msgid "Sale Taxes" msgid "Sale Taxes"
msgstr "Taxes à la vente" msgstr "Taxes à la vente"
#. module: product_category_tax #. module: product_category_tax
#: code:addons/product_category_tax/product.py:0 #: code:addons/product_category_tax/product.py:57
#, python-format #, python-format
msgid "" msgid "The purchase taxes configured on the product '%s' are not the same as the purchase taxes configured on it's related product category '%s'."
"The purchase taxes configured on the product '%s' are not the same as the " msgstr "Les taxes fournisseurs paramétrées sur l'article '%s' ne sont pas les mêmes que les taxes fournisseurs paramétrées sur la catégorie interne '%s'."
"purchase taxes configured on it's related internal category '%s'."
msgstr ""
"Les taxes à l'achat configurées sur l'article '%s' ne sont pas les mêmes que "
"les taxes à l'achat configurées sur sa catégorie interne '%s'."
#. module: product_category_tax #. module: product_category_tax
#: code:addons/product_category_tax/product.py:0 #: code:addons/product_category_tax/product.py:49
#, python-format #, python-format
msgid "" msgid "The sale taxes configured on the product '%s' are not the same as the sale taxes configured on it's related product category '%s'."
"The sale taxes configured on the product '%s' are not the same as the sale " msgstr "Les taxes à la vente paramétrées sur l'article '%s' ne sont pas les mêmes que les taxes à la vente paramétrées sur la catégorie interne '%s'."
"taxes configured on it's related internal category '%s'."
msgstr ""
"Les taxes à la vente configurées sur l'article '%s' ne sont pas les mêmes que "
"les taxes à la vente configurées sur sa catégorie interne '%s'."

View File

@@ -10,4 +10,4 @@ class ProductTemplate(models.Model):
detailed_type = fields.Selection(selection_add=[ detailed_type = fields.Selection(selection_add=[
('product', 'Storable Product') ('product', 'Storable Product')
], ondelete={'product': 'set default'}, default='product') ], ondelete={'product': 'set default'})

View File

@@ -32,7 +32,7 @@ This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>. <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', 'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability', 'website': 'http://www.akretion.com',
# We depend on point_of_sale and not only 'product' # We depend on point_of_sale and not only 'product'
# because the price barcode rule is added by the point_of_sale module # because the price barcode rule is added by the point_of_sale module
# (the weight barcode rule is added by the stock module) # (the weight barcode rule is added by the stock module)
@@ -46,7 +46,6 @@ This module has been written by Alexis de Lattre from Akretion
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'wizard/product_print_zpl_barcode_view.xml', 'wizard/product_print_zpl_barcode_view.xml',
'views/product.xml', 'views/product.xml',
'views/stock_picking.xml',
'data/barcode_sequence.xml', 'data/barcode_sequence.xml',
], ],
'installable': True, 'installable': True,

View File

@@ -1,412 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * product_print_zpl_barcode
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-15 13:39+0000\n"
"PO-Revision-Date: 2023-07-15 13:39+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__copies
msgid "# Labels"
msgstr "Nb étiquettes"
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__label_size__38x25
msgid "38x25 mm"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode
msgid "Barcode"
msgstr "Code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__nomenclature_id
msgid "Barcode Nomenclature"
msgstr "Nomenclature des codes-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__rule_id
msgid "Barcode Rule"
msgstr "Règle de codes-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode_type
msgid "Barcode Type"
msgstr "Type de code-barres"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Cancel"
msgstr "Annuler"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Close"
msgstr "Fermer"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__company_id
msgid "Company"
msgstr "Société"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_date
msgid "Created on"
msgstr "Créé le"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__currency_id
msgid "Currency"
msgstr "Devise"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "Default unit of measure used for all stock operations."
msgstr ""
"Unité de mesure par défaut utilisée pour toutes les opérations de stock."
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__display_name
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid ""
"Enable that option for products for which you must print a barcode upon "
"reception in stock."
msgstr ""
"Activez cette option sur les articles pour lesquels vous devez imprimer un "
"code-barres dès leur réception en stock."
#. module: product_print_zpl_barcode
#: model:ir.actions.act_window,name:product_print_zpl_barcode.product_print_zpl_barcode_action
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Generate Barcode"
msgstr "Générer un code-barres"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Generate Labels"
msgstr "Générer les étiquettes"
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode
msgid "Generate and print product barcodes in ZPL"
msgstr "Générer et imprimer des codes-barres d'articles en ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__id
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__id
msgid "ID"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__label_size
msgid "Label Size"
msgstr "Taille de l'étiquette"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode____last_update
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': barcode '%s' has %d digits. This wizard only supports EAN8 and "
"EAN13 for the moment."
msgstr ""
"Ligne '%s' : le code-barres '%s' comporte %d chiffres. Cet assistant ne "
"prend en charge que les EAN8 et EAN13 pour le moment."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Line '%s': barcode type '%s' is not supported for the moment"
msgstr ""
"Ligne '%s' : le type de code-barres '%s' n'est pas supporté pour le moment"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': the barcode '%s' is not a valid EAN barcode (wrong checksum)."
msgstr ""
"Ligne '%s' : le code-barres '%s' n'est pas un code-barres EAN valide "
"(mauvaise somme de contrôle)."
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode_line
msgid "Line of the print ZPL barcode wizard"
msgstr "Ligne de l'assistant d'impression du code-barres ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__line_ids
msgid "Lines"
msgstr "Lignes"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Missing Products"
msgstr "Produits manquants"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid "Must Print Barcode"
msgstr "Code-barres à imprimer"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "On line '%s', the number of copies must be strictly positive."
msgstr ""
"Sur la ligne '%s', le nombre d'étiquettes doit être strictement positif."
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_png
msgid "PNG Barcode Image"
msgstr "Image PNG du code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__parent_id
msgid "Parent"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price
msgid "Price"
msgstr "Prix"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price_uom
msgid "Price/UoM"
msgstr "Prix/unité"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__pricelist_id
msgid "Pricelist"
msgstr "Liste de prix"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Print"
msgstr "Imprimer"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Print Barcode"
msgstr "Imprimer le code-barres"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_product_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.view_picking_form
msgid "Print Barcodes"
msgstr "Imprimer les code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_template
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_id
msgid "Product"
msgstr "Article"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_name
msgid "Product Label"
msgstr "Étiquette de l'article"
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_product
msgid "Product Variant"
msgstr "Variante d'article"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__quantity
msgid "Qty"
msgstr "Qté"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_svg
msgid "SVG Barcode Image"
msgstr "Image SVG du code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_stock_picking__show_print_zpl_barcode
msgid "Show Print Zpl Barcode"
msgstr "Afficher le bouton imprimer le code-barres ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__state
msgid "State"
msgstr "État"
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step1
msgid "Step1"
msgstr "Étape 1"
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step2
msgid "Step2"
msgstr "Étape 2"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode of the product (%s) has %d characters, which is smaller than the"
" %d characters of the prefix of the barcode pattern (%s)."
msgstr ""
"Le code-barres de l'article (%s) comporte %d caractères, ce qui est plus "
"petit que les %d caractères du préfixe du modèle de code-barres (%s)."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode rule '%s' has a pattern '%s' which doesn't contain a integer and"
" decimal part between '{}'."
msgstr ""
"La règle de code-barres '%s' a un motif '%s' qui ne contient pas de partie "
"entière et décimale entre '{}'."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid "The product '%s' already has a barcode."
msgstr "L'article '%s' a déjà un code-barres."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "The quantity (%s) must be positive !"
msgstr "La quantité (%s) doit être positive !"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"The sequence 'private.product.barcode' is not properly configured. The "
"generated sequence should have 7 digits (for EAN-8) or 12 digits (for "
"EAN-13). It currently has %d digits."
msgstr ""
"La séquence 'private.product.barcode' n'est pas correctement configurée. La "
"séquence générée devrait avoir 7 chiffres (pour EAN-8) ou 12 chiffres (pour "
"EAN-13). Elle comporte actuellement %d chiffres."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The value to encode in the barcode (%s) is superior to the maximum value "
"allowed by the barcode pattern (%s)."
msgstr ""
"La valeur à encoder dans le code-barres (%s) est supérieure à la valeur "
"maximale autorisée par le motif du code-barres (%s)."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "There are no pricelist in company '%s'."
msgstr "Il n'y a pas de liste de prix dans la société '%s'."
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_stock_picking
msgid "Transfer"
msgstr "Transfert"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "UoM"
msgstr "Unité"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Wrong active_model in context (%s)."
msgstr "Mauvais active_model dans le contexte (%s)."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"You cannot call the method generate_barcode_from_product_template on product"
" '%s' because it has %d variants and not just one."
msgstr ""
"Vous ne pouvez pas appeler la méthode generate_barcode_from_product_template"
" sur l'article '%s' parce qu'il a %d variantes et non une seule."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "You must select a ZPL Printer."
msgstr "Vous devez sélectionner une imprimante ZPL."
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_file
msgid "ZPL File"
msgstr "Fichier ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_filename
msgid "ZPL Filename"
msgstr "Nom du fichier ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_printer_id
msgid "ZPL Printer"
msgstr "Imprimante ZPL"

View File

@@ -1,392 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * product_print_zpl_barcode
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-15 13:39+0000\n"
"PO-Revision-Date: 2023-07-15 13:39+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__copies
msgid "# Labels"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__label_size__38x25
msgid "38x25 mm"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode
msgid "Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__nomenclature_id
msgid "Barcode Nomenclature"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__rule_id
msgid "Barcode Rule"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode_type
msgid "Barcode Type"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Cancel"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Close"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__company_id
msgid "Company"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_uid
msgid "Created by"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_date
msgid "Created on"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__currency_id
msgid "Currency"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "Default unit of measure used for all stock operations."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__display_name
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__display_name
msgid "Display Name"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid ""
"Enable that option for products for which you must print a barcode upon "
"reception in stock."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.actions.act_window,name:product_print_zpl_barcode.product_print_zpl_barcode_action
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Generate Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Generate Labels"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode
msgid "Generate and print product barcodes in ZPL"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__id
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__id
msgid "ID"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__label_size
msgid "Label Size"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode____last_update
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line____last_update
msgid "Last Modified on"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': barcode '%s' has %d digits. This wizard only supports EAN8 and "
"EAN13 for the moment."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Line '%s': barcode type '%s' is not supported for the moment"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': the barcode '%s' is not a valid EAN barcode (wrong checksum)."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode_line
msgid "Line of the print ZPL barcode wizard"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__line_ids
msgid "Lines"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Missing Products"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid "Must Print Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "On line '%s', the number of copies must be strictly positive."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_png
msgid "PNG Barcode Image"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__parent_id
msgid "Parent"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price
msgid "Price"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price_uom
msgid "Price/UoM"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__pricelist_id
msgid "Pricelist"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Print"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Print Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_product_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.view_picking_form
msgid "Print Barcodes"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_template
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_id
msgid "Product"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_name
msgid "Product Label"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_product
msgid "Product Variant"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__quantity
msgid "Qty"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_svg
msgid "SVG Barcode Image"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_stock_picking__show_print_zpl_barcode
msgid "Show Print Zpl Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__state
msgid "State"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step1
msgid "Step1"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step2
msgid "Step2"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode of the product (%s) has %d characters, which is smaller than the"
" %d characters of the prefix of the barcode pattern (%s)."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode rule '%s' has a pattern '%s' which doesn't contain a integer and"
" decimal part between '{}'."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid "The product '%s' already has a barcode."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "The quantity (%s) must be positive !"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"The sequence 'private.product.barcode' is not properly configured. The "
"generated sequence should have 7 digits (for EAN-8) or 12 digits (for "
"EAN-13). It currently has %d digits."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The value to encode in the barcode (%s) is superior to the maximum value "
"allowed by the barcode pattern (%s)."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "There are no pricelist in company '%s'."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_stock_picking
msgid "Transfer"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "UoM"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Wrong active_model in context (%s)."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"You cannot call the method generate_barcode_from_product_template on product"
" '%s' because it has %d variants and not just one."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "You must select a ZPL Printer."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_file
msgid "ZPL File"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_filename
msgid "ZPL Filename"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_printer_id
msgid "ZPL Printer"
msgstr ""

View File

@@ -1,2 +1 @@
from . import product from . import product
from . import stock_picking

View File

@@ -29,6 +29,22 @@ class ProductTemplate(models.Model):
% (self.display_name, self.product_variant_count)) % (self.display_name, self.product_variant_count))
return self.product_variant_ids[0].generate_barcode_from_product_product() return self.product_variant_ids[0].generate_barcode_from_product_product()
def print_zpl_barcode_from_product_template(self):
self.ensure_one()
if self.product_variant_count != 1:
raise UserError(_(
"You cannot call the method "
"print_zpl_barcode_from_product_template on product '%s' "
"because it has %d variants and not just one.")
% (self.display_name, self.product_variant_count))
action = self.env["ir.actions.actions"]._for_xml_id(
'product_print_zpl_barcode.product_print_zpl_barcode_action')
action['context'] = {
'active_id': self.product_variant_ids[0].id,
'active_model': 'product.product',
}
return action
class ProductProduct(models.Model): class ProductProduct(models.Model):
_inherit = 'product.product' _inherit = 'product.product'

View File

@@ -1,25 +0,0 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo.tools import float_compare
class StockPicking(models.Model):
_inherit = "stock.picking"
show_print_zpl_barcode = fields.Boolean(compute='_compute_show_print_zpl_barcode')
@api.depends('state')
def _compute_show_print_zpl_barcode(self):
prec = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for picking in self:
show = False
if picking.state == 'done' and picking.picking_type_code != 'outgoing':
for line in picking.move_line_ids:
if (
line.product_id.must_print_barcode and
float_compare(line.qty_done, 0, precision_digits=prec) > 0):
show = True
picking.show_print_zpl_barcode = show

View File

@@ -1,3 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_product_print_zpl_barcode,Full access to product.print.zpl.barcode wizard,model_product_print_zpl_barcode,base_report_to_printer.printing_group_user,1,1,1,1 access_product_print_zpl_barcode,Full access to product.print.zpl.barcode wizard,model_product_print_zpl_barcode,base_report_to_printer.printing_group_user,1,1,1,1
access_product_print_zpl_barcode_line,Full access to product.print.zpl.barcode.line wizard,model_product_print_zpl_barcode_line,base_report_to_printer.printing_group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_product_print_zpl_barcode Full access to product.print.zpl.barcode wizard model_product_print_zpl_barcode base_report_to_printer.printing_group_user 1 1 1 1
access_product_print_zpl_barcode_line Full access to product.print.zpl.barcode.line wizard model_product_print_zpl_barcode_line base_report_to_printer.printing_group_user 1 1 1 1

View File

@@ -27,23 +27,11 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<header position="inside"> <header position="inside">
<button name="generate_barcode_from_product_template" type="object" string="Generate Barcode" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '!=', False)]}"/> <button name="generate_barcode_from_product_template" type="object" string="Generate Barcode" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '!=', False)]}"/>
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcode" groups="base_report_to_printer.printing_group_user" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '=', False)]}"/> <button name="print_zpl_barcode_from_product_template" type="object" string="Print Barcode" groups="base_report_to_printer.printing_group_user" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '=', False)]}"/>
</header> </header>
</field> </field>
</record> </record>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view"/>
<field name="arch" type="xml">
<field name="product_variant_count" position="before">
<header>
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcodes" groups="base_report_to_printer.printing_group_user"/>
</header>
</field>
</field>
</record>
<record id="product_normal_form_view" model="ir.ui.view"> <record id="product_normal_form_view" model="ir.ui.view">
<field name="model">product.product</field> <field name="model">product.product</field>
@@ -56,16 +44,5 @@
</field> </field>
</record> </record>
<record id="product_product_tree_view" model="ir.ui.view">
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view"/>
<field name="arch" type="xml">
<field name="default_code" position="before">
<header>
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcodes" groups="base_report_to_printer.printing_group_user"/>
</header>
</field>
</field>
</record>
</odoo> </odoo>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_picking_form" model="ir.ui.view">
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<button name="action_toggle_is_locked" position="after">
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcodes" attrs="{'invisible': [('show_print_zpl_barcode', '=', False)]}"/>
<field name="show_print_zpl_barcode" invisible="1"/>
</button>
</field>
</record>
</odoo>

View File

@@ -1,4 +1,4 @@
# Copyright 2016-2023 Akretion France (http://www.akretion.com/) # Copyright 2016-2020 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -9,18 +9,23 @@ from stdnum.ean import is_valid, calc_check_digit
import base64 import base64
import re import re
import logging
logger = logging.getLogger(__name__)
class ProductPrintZplBarcode(models.TransientModel): class ProductPrintZplBarcode(models.TransientModel):
_name = 'product.print.zpl.barcode' _name = 'product.print.zpl.barcode'
_description = 'Generate and print product barcodes in ZPL' _description = 'Generate and print product barcodes in ZPL'
_check_company_auto = True
@api.model @api.model
def default_get(self, fields_list): def default_get(self, fields_list):
res = super().default_get(fields_list) res = super().default_get(fields_list)
assert self._context.get('active_model') == 'product.product',\
'wrong active_model, should be product.product'
product_id = self._context.get('active_id')
product = self.env['product.product'].browse(product_id)
if not product:
raise UserError(_('Missing Product'))
if not product.barcode:
raise UserError(_(
"Product '%s' doesn't have a barcode") % product.display_name)
nomenclature = self.env.ref('barcodes.default_barcode_nomenclature') nomenclature = self.env.ref('barcodes.default_barcode_nomenclature')
company = self.env.company company = self.env.company
posconfig = self.env['pos.config'].sudo().search( posconfig = self.env['pos.config'].sudo().search(
@@ -34,200 +39,82 @@ class ProductPrintZplBarcode(models.TransientModel):
], limit=1) ], limit=1)
if not pricelist: if not pricelist:
raise UserError(_( raise UserError(_(
"There are no pricelist in company '%s'.") % company.name) "There are no pricelist in company %s ?") % company.name)
printer = self.env['printing.printer'].get_default() printer = self.env['printing.printer'].get_default()
line_ids = []
if self._context.get('active_model') == 'product.product':
product_ids = self._context.get('active_ids')
products = self.env['product.product'].browse(product_ids)
if not products:
raise UserError(_('Missing Products'))
for product in products:
self._update_line_ids(line_ids, product)
elif self._context.get('active_model') == 'product.template':
product_tmpl_ids = self._context.get('active_ids')
product_tmpls = self.env['product.template'].browse(product_tmpl_ids)
for product_tmpl in product_tmpls:
for product in product_tmpl.product_variant_ids:
self._update_line_ids(line_ids, product)
elif self._context.get('active_model') == 'stock.picking':
prec = self.env['decimal.precision'].precision_get(
'Product Unit of Measure')
picking = self.env['stock.picking'].browse(self._context['active_id'])
for ml in picking.move_line_ids:
if (
ml.product_id and
ml.product_id.must_print_barcode and
float_compare(ml.qty_done, 0, precision_digits=prec) > 0):
self._update_line_ids(
line_ids, ml.product_id, int(round(ml.qty_done)))
else:
raise UserError(_(
"Wrong active_model in context (%s).")
% self._context.get('active_model'))
res.update({ res.update({
'company_id': company.id,
'nomenclature_id': nomenclature.id, 'nomenclature_id': nomenclature.id,
'pricelist_id': pricelist.id, 'pricelist_id': pricelist.id,
'currency_id': pricelist.currency_id.id,
'barcode': product.barcode,
'product_name': product.name,
'product_id': product_id,
'zpl_printer_id': printer and printer.id or False, 'zpl_printer_id': printer and printer.id or False,
'line_ids': line_ids,
}) })
return res return res
@api.model product_id = fields.Many2one(
def _update_line_ids(self, line_ids, product, copies=1): 'product.product', string='Product', required=True, readonly=True)
if product.barcode: uom_id = fields.Many2one(related='product_id.uom_id')
line_ids.append((0, 0, { # 1 line = un peu moins de 30
'barcode': product.barcode, product_name = fields.Char('Product Label', required=True, size=56)
'product_name': product.name,
'product_id': product.id,
'copies': copies,
}))
else:
logger.warning("Product '%s' doesn't have a barcode", product.display_name)
company_id = fields.Many2one( # default value set by default_get
'res.company', required=True, ondelete='cascade')
nomenclature_id = fields.Many2one( nomenclature_id = fields.Many2one(
'barcode.nomenclature', 'Barcode Nomenclature', required=True, 'barcode.nomenclature', 'Barcode Nomenclature', required=True)
states={'step2': [('readonly', True)]}) rule_id = fields.Many2one(
# label_size: remove readonly=True when we will support more labels 'barcode.rule', string='Barcode Rule', readonly=True,
compute='_compute_rule_id')
barcode_type = fields.Selection(related='rule_id.type', string="Barcode Type")
label_size = fields.Selection([ label_size = fields.Selection([
('38x25', '38x25 mm'), ('38x25', '38x25 mm'),
], required=True, default='38x25', readonly=True) ], required=True, default='38x25')
pricelist_id = fields.Many2one( pricelist_id = fields.Many2one(
'product.pricelist', string='Pricelist', required=True, 'product.pricelist', string='Pricelist', required=True)
states={'step2': [('readonly', True)]}, check_company=True, currency_id = fields.Many2one(related='pricelist_id.currency_id')
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" # TODO: for the moment, we only support weight, but...
) quantity = fields.Float(digits='Stock Weight')
price_uom = fields.Monetary(
readonly=True, string="Price per Unit of Measure",
compute='_compute_price') # given by pricelist
price = fields.Monetary(compute='_compute_price', readonly=True)
currency_id = fields.Many2one('res.currency', string='Currency')
state = fields.Selection([ state = fields.Selection([
('step1', 'Step1'), ('step1', 'Step1'),
('step2', 'Step2'), ('step2', 'Step2'),
], default='step1', readonly=True) ], default='step1', readonly=True)
zpl_file = fields.Binary(string='ZPL File', readonly=True) zpl_file = fields.Binary(string='ZPL File', readonly=True)
zpl_filename = fields.Char('ZPL Filename') zpl_filename = fields.Char('ZPL Filename')
barcode = fields.Char(readonly=True)
copies = fields.Integer(
string='Number of Labels', default=1, required=True)
zpl_printer_id = fields.Many2one( zpl_printer_id = fields.Many2one(
'printing.printer', string='ZPL Printer') 'printing.printer', string='ZPL Printer')
line_ids = fields.One2many(
'product.print.zpl.barcode.line', 'parent_id',
string='Lines', states={'step2': [('readonly', True)]})
def generate(self): @api.depends('pricelist_id', 'quantity', 'product_id')
"""Called by button for the wizard, 1st step"""
self.ensure_one()
zpl_strings = []
for line in self.line_ids:
barcode = line.barcode
product_name = line.product_name
assert barcode
barcode_len = len(barcode)
if barcode_len not in (8, 13):
raise UserError(_(
"Line '%s': barcode '%s' has %d digits. "
"This wizard only supports EAN8 and EAN13 for the moment.")
% (product_name, barcode, barcode_len))
if not is_valid(barcode):
raise UserError(_(
"Line '%s': the barcode '%s' is not a valid EAN barcode "
"(wrong checksum).") % (product_name, barcode))
if line.copies <= 0:
raise UserError(_(
"On line '%s', the number of copies must be strictly positive."
) % product_name)
if line.barcode_type in ('price', 'weight'):
barcode, zpl_str = line._prepare_price_weight_barcode_type()
elif line.barcode_type == 'product':
barcode, zpl_str = line._prepare_product_barcode_type()
else:
raise UserError(_(
"Line '%s': barcode type '%s' is not supported for the moment")
% (product_name, line.barcode_type))
line.write({'barcode': barcode})
zpl_strings.append(zpl_str)
zpl_filename = "barcodes.zpl"
if len(self.line_ids) == 1:
zpl_filename = "barcode_%s.zpl" % self.line_ids[0].barcode
zpl_str = '\n'.join(zpl_strings)
zpl_bytes = zpl_str.encode('utf-8')
vals = {
'zpl_file': base64.encodebytes(zpl_bytes),
'state': 'step2',
'zpl_filename': zpl_filename,
}
self.write(vals)
action = self.env["ir.actions.actions"]._for_xml_id(
'product_print_zpl_barcode.product_print_zpl_barcode_action')
action.update({
'res_id': self.id,
'context': self._context,
'views': False})
return action
def print_zpl(self):
if not self.zpl_printer_id:
raise UserError(_(
"You must select a ZPL Printer."))
self.zpl_printer_id.print_document(
self.zpl_filename, base64.decodebytes(self.zpl_file), format='raw')
class ProductPrintZplBarcodeLine(models.TransientModel):
_name = 'product.print.zpl.barcode.line'
_description = 'Line of the print ZPL barcode wizard'
parent_id = fields.Many2one(
'product.print.zpl.barcode', ondelete='cascade')
product_id = fields.Many2one(
'product.product', string='Product', readonly=True)
uom_id = fields.Many2one(related='product_id.uom_id', string='UoM')
# 1 line = a bit less than 30
# I don't make product_name a stored computed field because I'm afraid
# that we may not take the lang of the user
product_name = fields.Char('Product Label', required=True, size=56)
rule_id = fields.Many2one(
'barcode.rule', string='Barcode Rule', compute='_compute_rule_id')
barcode_type = fields.Selection(related='rule_id.type', string="Barcode Type")
currency_id = fields.Many2one(related='parent_id.pricelist_id.currency_id')
# TODO: for the moment, we only support weight, but...
quantity = fields.Float(digits='Stock Weight', string='Qty')
price_uom = fields.Monetary(
string="Price/UoM", compute='_compute_price') # given by pricelist
price = fields.Monetary(compute='_compute_price')
barcode = fields.Char(readonly=True)
copies = fields.Integer(string='# Labels', default=1, required=True)
@api.depends('parent_id.pricelist_id', 'quantity', 'product_id')
def _compute_price(self): def _compute_price(self):
# for regular barcodes # for regular barcodes
for line in self: for wiz in self:
pricelist = line.parent_id.pricelist_id if wiz.pricelist_id and wiz.product_id:
price_uom = price = 0.0 price_uom = wiz.pricelist_id.get_product_price(
if pricelist and line.product_id: wiz.product_id, 1, False)
price_uom = pricelist.get_product_price(line.product_id, 1, False) wiz.price_uom = price_uom
price = price_uom * line.quantity wiz.price = price_uom * wiz.quantity
line.price_uom = price_uom
line.price = price
@api.depends('parent_id.nomenclature_id') @api.depends('nomenclature_id')
def _compute_rule_id(self): def _compute_rule_id(self):
for line in self: for wiz in self:
nomenclature = line.parent_id.nomenclature_id
match_rule = False match_rule = False
if nomenclature and line.barcode: if wiz.nomenclature_id and wiz.barcode:
for rule in nomenclature.rule_ids: for rule in wiz.nomenclature_id.rule_ids:
match = nomenclature.match_pattern( match = wiz.nomenclature_id.match_pattern(
line.barcode, rule.pattern) wiz.barcode, rule.pattern)
if match.get('match'): if match.get('match'):
match_rule = rule.id match_rule = rule.id
break break
line.rule_id = match_rule wiz.rule_id = match_rule
def _prepare_price_weight_barcode_type(self): def _prepare_price_weight_barcode_type(self):
dpo = self.env['decimal.precision'] dpo = self.env['decimal.precision']
bno = self.env['barcode.nomenclature']
prec = dpo.precision_get('Stock Weight') prec = dpo.precision_get('Stock Weight')
value = self.quantity value = self.quantity
pbarcode = self.barcode pbarcode = self.barcode
@@ -252,7 +139,7 @@ class ProductPrintZplBarcodeLine(models.TransientModel):
barcode = pbarcode[0:len(prefix)] barcode = pbarcode[0:len(prefix)]
# print("barcode=", barcode) # print("barcode=", barcode)
# print("pattern=", pattern) # print("pattern=", pattern)
m = re.search(r'\{N+D+\}', pattern) m = re.search('\{N+D+\}', pattern)
# print("m=", m) # print("m=", m)
assert m assert m
pattern_val = m.group(0) pattern_val = m.group(0)
@@ -285,7 +172,7 @@ class ProductPrintZplBarcodeLine(models.TransientModel):
assert len(barcode) == 13 assert len(barcode) == 13
assert is_valid(barcode) assert is_valid(barcode)
# print("barcode FINAL=", barcode) # print("barcode FINAL=", barcode)
zpl_str = self._price_weight_barcode_type_zpl() % { zpl_unicode = self._price_weight_barcode_type_zpl() % {
'product_name': self.product_name, 'product_name': self.product_name,
'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE', 'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE',
'ean_no_checksum': barcode[:-1], 'ean_no_checksum': barcode[:-1],
@@ -296,7 +183,12 @@ class ProductPrintZplBarcodeLine(models.TransientModel):
'quantity': value, 'quantity': value,
'uom_name': self.uom_id.name, 'uom_name': self.uom_id.name,
} }
return (barcode, zpl_str) zpl_bytes = zpl_unicode.encode('utf-8')
vals = {
'zpl_file': base64.encodebytes(zpl_bytes),
'barcode': barcode,
}
return vals
@api.model @api.model
def _price_weight_barcode_type_zpl(self): def _price_weight_barcode_type_zpl(self):
@@ -337,7 +229,7 @@ class ProductPrintZplBarcodeLine(models.TransientModel):
return label return label
def _prepare_product_barcode_type(self): def _prepare_product_barcode_type(self):
zpl_str = self._product_barcode_type_zpl() % { zpl_unicode = self._product_barcode_type_zpl() % {
'product_name': self.product_name, 'product_name': self.product_name,
'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE', 'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE',
'ean_no_checksum': self.barcode[:-1], 'ean_no_checksum': self.barcode[:-1],
@@ -345,4 +237,60 @@ class ProductPrintZplBarcodeLine(models.TransientModel):
'currency_symbol': self.currency_id.symbol, # symbol is a required field 'currency_symbol': self.currency_id.symbol, # symbol is a required field
'copies': self.copies, 'copies': self.copies,
} }
return (self.barcode, zpl_str) zpl_bytes = zpl_unicode.encode('utf-8')
vals = {
'zpl_file': base64.encodebytes(zpl_bytes),
'barcode': self.barcode, # unchanged
}
return vals
def generate(self):
assert self.barcode
if len(self.barcode) not in (8, 13):
raise UserError(_(
"This wizard only supports EAN8 and EAN13 for the moment. "
"Barcode '%s' has %d digits.") % (
self.barcode,
len(self.barcode)))
if not is_valid(self.barcode):
raise UserError(_(
"The barcode '%s' is not a valid EAN barcode "
"(wrong checksum).") % self.barcode)
if not self.copies:
raise UserError(_("The number of copies cannot be 0"))
if self.barcode_type in ('price', 'weight'):
vals = self._prepare_price_weight_barcode_type()
elif self.barcode_type == 'product':
vals = self._prepare_product_barcode_type()
else:
raise UserError(_(
"Barcode Type %s is not supported for the moment")
% self.barcode_type)
vals.update({
'state': 'step2',
'zpl_filename': 'barcode_%s.zpl' % vals['barcode'],
})
self.write(vals)
action = self.env["ir.actions.actions"]._for_xml_id(
'product_print_zpl_barcode.product_print_zpl_barcode_action')
action.update({
'res_id': self.id,
'context': self._context,
'views': False})
return action
def print_zpl(self):
if not self.zpl_printer_id:
raise UserError(_(
"You must select a ZPL Printer."))
self.zpl_printer_id.print_document(
self.zpl_filename, base64.decodebytes(self.zpl_file), format='raw')
action = True
if self._context.get('print_and_new'):
action = self.env["ir.actions.actions"]._for_xml_id(
'product_print_zpl_barcode.product_print_zpl_barcode_action')
action.update({
'views': False,
'context': self._context,
})
return action

View File

@@ -11,41 +11,38 @@
<field name="name">product_print_zpl_barcode.form</field> <field name="name">product_print_zpl_barcode.form</field>
<field name="model">product.print.zpl.barcode</field> <field name="model">product.print.zpl.barcode</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form string="Generate and Print Product Barcode">
<group name="step1"> <group name="step1" string="Configuration">
<field name="state" invisible="1"/> <field name="state" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/> <field name="currency_id" invisible="1"/>
<field name="company_id" invisible="1"/> <field name="product_id"/>
<field name="product_name" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="pricelist_id" attrs="{'readonly': [('state', '=', 'step2')]}"/> <field name="pricelist_id" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="price_uom"/>
<field name="label_size" attrs="{'readonly': [('state', '=', 'step2')]}"/> <field name="label_size" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="nomenclature_id" attrs="{'readonly': [('state', '=', 'step2')]}"/> <field name="nomenclature_id" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="rule_id"/>
<field name="barcode_type"/>
<field name="barcode"/>
<field name="copies" attrs="{'readonly': [('state', '=', 'step2')]}"/>
</group> </group>
<group name="step2" states="step2"> <group string="Enter Quantity" attrs="{'invisible': [('barcode_type', '=', 'product')]}">
<div name="qty_uom">
<field name="quantity" attrs="{'readonly': [('state', '=', 'step2')]}" class="oe_inline"/>
<field name="uom_id" class="oe_inline"/>
</div>
</group>
<group name="step2" states="step2" string="Label">
<field name="price" attrs="{'invisible': [('barcode_type', 'not in', ('price', 'weight'))]}"/>
<field name="zpl_file" filename="zpl_filename" /> <field name="zpl_file" filename="zpl_filename" />
<field name="zpl_filename" invisible="1"/> <field name="zpl_filename" invisible="1"/>
<field name="zpl_printer_id" attrs="{'required': [('state', '=', 'step2')]}"/> <field name="zpl_printer_id" attrs="{'required': [('state', '=', 'step2')]}"/>
</group> </group>
<group name="lines">
<field name="line_ids" colspan="2" nolabel="1">
<tree editable="bottom">
<field name="currency_id" invisible="1"/>
<field name="product_id" optional="hide" force_save="1"/>
<field name="product_name"/>
<field name="price_uom"/>
<field name="rule_id" optional="show"/>
<field name="barcode_type" optional="hide"/>
<field name="barcode" force_save="1"/>
<field name="price" attrs="{'invisible': [('barcode_type', 'not in', ('price', 'weight'))]}"/>
<field name="quantity" attrs="{'invisible': [('barcode_type', '=', 'product')]}" optional="show"/>
<field name="uom_id" attrs="{'invisible': [('barcode_type', '=', 'product')]}" optional="show"/>
<field name="copies" />
</tree>
</field>
</group>
<footer> <footer>
<button name="generate" type="object" string="Generate Labels" class="btn-primary" states="step1"/> <button name="generate" type="object" string="Generate Label" class="btn-primary" states="step1"/>
<button special="cancel" string="Cancel" class="btn-default" states="step1"/> <button special="cancel" string="Cancel" class="btn-default" states="step1"/>
<button name="print_zpl" type="object" string="Print" class="btn-primary" states="step2"/> <button name="print_zpl" type="object" string="Print" class="btn-primary" states="step2"/>
<button name="print_zpl" type="object" string="Print and New" class="btn-primary" context="{'print_and_new': True}" attrs="{'invisible': ['|', ('state', '!=', 'step2'), ('barcode_type', '=', 'product')]}"/>
<button special="cancel" string="Close" class="btn-default" states="step2"/> <button special="cancel" string="Close" class="btn-default" states="step2"/>
</footer> </footer>
</form> </form>

View File

@@ -15,8 +15,6 @@ Product Priority Star
This module adds a priority star on products (like on pickings and manufacturing order). If the star is yellow, the product will be displayed at the top. This module adds a priority star on products (like on pickings and manufacturing order). If the star is yellow, the product will be displayed at the top.
This feature is native in Odoo 16.0.
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>. This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', 'author': 'Akretion',

View File

@@ -37,7 +37,6 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
'views/product_product.xml', 'views/product_product.xml',
'views/product_config_menu.xml', 'views/product_config_menu.xml',
'views/product_category_view.xml', 'views/product_category_view.xml',
'views/product_attribute_view.xml',
], ],
'installable': True, 'installable': True,
} }

View File

@@ -3,4 +3,3 @@ from . import product_template
from . import product_supplierinfo from . import product_supplierinfo
from . import product_pricelist from . import product_pricelist
from . import product_category from . import product_category
from . import product_attribute

View File

@@ -1,27 +0,0 @@
# Copyright (C) 2022 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class ProductAttribute(models.Model):
_inherit = "product.attribute"
values_count = fields.Integer(compute="_compute_values_count")
@api.depends("value_ids")
def _compute_values_count(self):
for attr in self:
attr.values_count = len(attr.value_ids)
def show_values_ids(self):
return {
"name": "Attributes Lines",
"type": "ir.actions.act_window",
"res_id": self.id,
"view_mode": "tree",
"res_model": "product.attribute.value",
"view_id":self.env.ref("product_usability.product_attribute_value_view_tree").id,
"target": "current",
"domain": [("id", "in", self.value_ids.ids)],
}

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="product_attribute_values_button" model="ir.ui.view">
<field name="model">product.attribute</field>
<field name="inherit_id" ref="product.product_attribute_view_form" />
<field name="arch" type="xml">
<group name="main_fields" position='before'>
<div class="oe_button_box" name="button_box">
<button
name="show_values_ids"
type="object"
class="oe_stat_button"
icon="fa-tasks"
>
<field
name="values_count"
widget="statinfo"
string="Attribute Values"
/>
</button>
</div>
</group>
</field>
</record>
<record model="ir.ui.view" id="product_attribute_value_view_tree">
<field name="name">product.attribute.value.view.tree</field>
<field name="model">product.attribute.value</field>
<field name="arch" type="xml">
<tree string="Attributes Values">
<field name="name"/>
<field name="is_custom"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -32,13 +32,11 @@
<field name="model">product.pricelist.item</field> <field name="model">product.pricelist.item</field>
<field name="inherit_id" ref="product.product_pricelist_item_form_view"/> <field name="inherit_id" ref="product.product_pricelist_item_form_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="applied_on" position="before">
<field name="pricelist_id" position="move"/>
</field>
<field name="pricelist_id" position="attributes"> <field name="pricelist_id" position="attributes">
<attribute name="invisible">context.get('from_product_pricelist_view')</attribute> <attribute name="invisible">not context.get('product_pricelist_item_main_view')</attribute>
</field> </field>
</field> </field>
</record> </record>
</odoo> </odoo>

View File

@@ -13,7 +13,7 @@
<field name="res_model">product.pricelist.item</field> <field name="res_model">product.pricelist.item</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="domain">[('pricelist_id', '=', active_id)]</field> <field name="domain">[('pricelist_id', '=', active_id)]</field>
<field name="context">{'product_pricelist_item_main_view': True, 'default_pricelist_id': active_id}</field> <field name="context">{'product_pricelist_item_main_view': True}</field>
</record> </record>
<record id="product_pricelist_view" model="ir.ui.view"> <record id="product_pricelist_view" model="ir.ui.view">
@@ -34,10 +34,6 @@
</button> </button>
</div> </div>
</div> </div>
<field name="item_ids" position="attributes">
<attribute name="context">{'from_product_pricelist_view': True, 'default_base': 'list_price'}</attribute>
</field>
</field> </field>
</record> </record>

View File

@@ -32,16 +32,5 @@
</field> </field>
</record> </record>
<record id="product_product_tree_view" model="ir.ui.view">
<field name="name">usability.product.product.tree</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view"/>
<field name="arch" type="xml">
<field name="company_id" position="before">
<field name="seller_id" optional="hide"/>
</field>
</field>
</record>
</odoo> </odoo>

View File

@@ -14,7 +14,7 @@
<field name="inherit_id" ref="product.product_template_search_view" /> <field name="inherit_id" ref="product.product_template_search_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="categ_id" position="after"> <field name="categ_id" position="after">
<field name="seller_id" string="Main Supplier"/> <field name="seller_ids" string="Supplier" filter_domain="[('seller_ids.name', 'ilike', self)]"/>
</field> </field>
</field> </field>
</record> </record>
@@ -32,14 +32,4 @@
</field> </field>
</record> </record>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view"/>
<field name="arch" type="xml">
<field name="barcode" position="after">
<field name="seller_id" optional="hide"/>
</field>
</field>
</record>
</odoo> </odoo>

View File

@@ -1,20 +0,0 @@
# Copyright 2023 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Project Usability',
'version': '14.0.1.0.0',
'category': 'Services/Project',
'license': 'AGPL-3',
'summary': 'Usability improvements on project module',
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': [
'project',
],
'data': [
'views/project_project.xml',
],
'installable': True,
}

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<!-- user_id is not displayed by default in project kanban view (it is native in v16) -->
<record id="view_project_kanban" model="ir.ui.view">
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project_kanban"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="user_id"/>
</field>
<xpath expr="//div[hasclass('o_project_kanban_boxes')]" position="inside">
<a t-if="record.user_id.raw_value" class="o_project_kanban_box">
<field name="user_id" widget="many2one_avatar_user"/>
</a>
</xpath>
</field>
</record>
</odoo>

View File

@@ -23,9 +23,7 @@ Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for
'purchase_usability', 'purchase_usability',
], ],
'data': [ 'data': [
'views/purchase_order.xml',
'views/stock_picking.xml', 'views/stock_picking.xml',
'views/stock_warehouse_orderpoint.xml',
], ],
'installable': True, 'installable': True,
} }

View File

@@ -1,2 +1 @@
from . import purchase from . import purchase
from . import stock_warehouse_orderpoint

View File

@@ -1,21 +0,0 @@
# Copyright 2015-2020 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
# Field needed to be able to search on supplier in the "Replenish" tree view
# I put it in purchase_stock_usability and not stock_usability
# because I wanted to use the field 'show_supplier' defined in purchase_stock
# (but I don't use it in the end because its computation returns False even
# on products with a Buy route) and I may also
# one day interact with supplier_id (M2O product.supplierinfo) defined in
# purchase_stock
seller_id = fields.Many2one(
related='product_id.product_tmpl_id.seller_ids.name',
store=True, string='Supplier',
)

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="purchase_order_tree" model="ir.ui.view">
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_tree"/>
<field name="arch" type="xml">
<field name="date_planned" position="after">
<field name="picking_type_id" optional="hide"/>
</field>
</field>
</record>
<record id="purchase_order_view_tree" model="ir.ui.view">
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_view_tree"/>
<field name="arch" type="xml">
<field name="date_planned" position="after">
<field name="picking_type_id" optional="hide"/>
</field>
</field>
</record>
<record id="purchase_order_kpis_tree" model="ir.ui.view">
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_kpis_tree"/>
<field name="arch" type="xml">
<field name="date_planned" position="after">
<field name="picking_type_id" optional="hide"/>
</field>
</field>
</record>
</odoo>

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014-2020 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_warehouse_orderpoint_tree_editable" model="ir.ui.view">
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_tree_editable" />
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="seller_id" optional="hide"/>
</field>
</field>
</record>
<record id="view_warehouse_orderpoint_tree_editable_config" model="ir.ui.view">
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_tree_editable_config" />
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="seller_id" optional="hide"/>
</field>
</field>
</record>
<record id="stock_reorder_report_search" model="ir.ui.view">
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id" ref="stock.stock_reorder_report_search"/>
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="seller_id" domain="[('parent_id', '=', False)]"/>
</field>
<filter name="groupby_category" position="after">
<filter string="Supplier" name="seller_id_groupby" context="{'group_by': 'seller_id'}"/>
</filter>
</field>
</record>
</odoo>

View File

@@ -68,13 +68,6 @@ class PurchaseOrder(models.Model):
# ] # ]
return res return res
def _prepare_invoice(self):
# Don't write self.partner_ref on 'ref' of invoice... ref is for the
# supplier invoice number !
vals = super()._prepare_invoice()
vals["ref"] = ''
return vals
class PurchaseOrderLine(models.Model): class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line' _inherit = 'purchase.order.line'

View File

@@ -26,19 +26,18 @@
<field name="model">purchase.report</field> <field name="model">purchase.report</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="order_id" optional="show"/>
<field name="commercial_partner_id"/> <field name="commercial_partner_id"/>
<field name="date_order" optional="show"/> <field name="date_order"/>
<field name="date_approve" optional="show"/> <field name="date_approve"/>
<field name="product_id"/> <field name="product_id"/>
<field name="qty_ordered" sum="1"/> <field name="qty_ordered" sum="1"/>
<field name="qty_received" sum="1"/> <field name="qty_received" sum="1"/>
<field name="qty_billed" sum="1"/> <field name="qty_billed" sum="1"/>
<field name="product_uom" groups="uom.group_uom"/> <field name="product_uom"/>
<field name="price_total" sum="1"/> <field name="price_total" sum="1"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting" optional="show"/> <field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
<field name="currency_id" invisible="1"/> <field name="currency_id" invisible="1"/>
<field name="user_id" optional="hide"/> <field name="user_id"/>
<field name="state"/> <field name="state"/>
</tree> </tree>
</field> </field>

View File

@@ -7,7 +7,6 @@ from odoo import api, models
class CrmLead(models.Model): class CrmLead(models.Model):
_inherit = 'crm.lead' _inherit = 'crm.lead'
# not needed in v16 because it has been fixed in sale_crm
def action_view_sale_quotation(self): def action_view_sale_quotation(self):
action = super().action_view_sale_quotation() action = super().action_view_sale_quotation()
if 'search_default_partner_id' in action['context']: if 'search_default_partner_id' in action['context']:

View File

@@ -1,10 +1,10 @@
# Copyright 2019-2024 Akretion France (http://www.akretion.com) # Copyright 2019 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Sale Down Payment', 'name': 'Sale Down Payment',
'version': '14.0.1.0.0', 'version': '12.0.1.0.0',
'category': 'Sales', 'category': 'Sales',
'license': 'AGPL-3', 'license': 'AGPL-3',
'summary': 'Link payment to sale orders', 'summary': 'Link payment to sale orders',
@@ -14,7 +14,7 @@ Sale Down Payment
This module adds a link between payments and sale orders. It allows to see down payments directly on the sale order form view. This module adds a link between payments and sale orders. It allows to see down payments directly on the sale order form view.
After processing a bank statement, you can start a wizard to link unreconciled incoming payments to a sale order (NOT ported to v14 so far). There is also a button *Register Payment* on the sale order. After processing a bank statement, you can start a wizard to link unreconciled incoming payments to a sale order. There is also a button *Register Payment* on the sale order.
This module targets B2B companies that don't want to generate a down payment invoice for an advanced payment. This module targets B2B companies that don't want to generate a down payment invoice for an advanced payment.
@@ -25,13 +25,11 @@ This module has been written by Alexis de Lattre from Akretion
'website': 'http://www.akretion.com', 'website': 'http://www.akretion.com',
'depends': ['sale'], 'depends': ['sale'],
'data': [ 'data': [
'security/ir.model.access.csv', 'wizard/account_bank_statement_sale_view.xml',
'wizard/account_payment_register_sale_view.xml', 'views/account_bank_statement.xml',
# 'wizard/account_bank_statement_sale_view.xml',
# 'views/account_bank_statement.xml',
'views/sale.xml', 'views/sale.xml',
'views/account_move_line.xml', 'views/account_move_line.xml',
'views/account_payment.xml', 'views/account_payment.xml',
], ],
'installable': True, 'installable': False,
} }

View File

@@ -1,4 +1,3 @@
from . import sale from . import sale
from . import account_move
from . import account_move_line from . import account_move_line
from . import account_payment from . import account_payment

View File

@@ -1,26 +0,0 @@
# Copyright 2023-2024 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class AccountMove(models.Model):
_inherit = 'account.move'
def _post(self, soft=True):
res = super()._post(soft=soft)
amlo = self.env['account.move.line']
for move in self:
if move.state == 'posted' and move.move_type == 'out_invoice':
sales = move.invoice_line_ids.sale_line_ids.order_id
if sales:
mlines = amlo.search([('sale_id', 'in', sales.ids)])
if mlines:
mlines_to_reconcile = move.line_ids.filtered(
lambda line: line.account_id ==
move.commercial_partner_id.property_account_receivable_id)
mlines_to_reconcile |= mlines
mlines_to_reconcile.remove_move_reconcile()
mlines_to_reconcile.reconcile()
return res

View File

@@ -1,4 +1,4 @@
# Copyright 2019-2024 Akretion France (http://www.akretion.com) # Copyright 2019 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -9,23 +9,24 @@ from odoo.exceptions import ValidationError
class AccountMoveLine(models.Model): class AccountMoveLine(models.Model):
_inherit = 'account.move.line' _inherit = 'account.move.line'
sale_id = fields.Many2one( sale_id = fields.Many2one('sale.order', string='Sale Order')
'sale.order', string='Sale Order', check_company=True, account_internal_type = fields.Selection(
domain="[('partner_invoice_id', 'child_of', partner_id), ('state', '!=', 'cancel'), ('invoice_status', '!=', 'invoiced'), ('company_id', '=', company_id)]") related='account_id.user_type_id.type', store=True,
string='Account Internal Type')
@api.constrains('sale_id', 'account_id') @api.constrains('sale_id', 'account_id')
def _sale_id_check(self): def sale_id_check(self):
for line in self: for line in self:
if line.sale_id and line.account_internal_type != 'receivable': if line.sale_id and line.account_id.internal_type != 'receivable':
raise ValidationError(_( raise ValidationError(_(
"The account move line '%s' is linked to sale order '%s' " "The account move line '%s' is linked to sale order '%s' "
"but it uses account '%s' which is not a receivable " "but it uses account '%s' which is not a receivable "
"account.") "account.")
% (line.display_name, % (line.name,
line.sale_id.display_name, line.sale_id.name,
line.account_id.display_name)) line.account_id.display_name))
@api.onchange('account_id') @api.onchange('account_id')
def sale_advance_payement_account_id_change(self): def sale_advance_payement_account_id_change(self):
if self.sale_id and self.account_internal_type != 'receivable': if self.sale_id and self.account_id.user_type_id.type != 'receivable':
self.sale_id = False self.sale_id = False

View File

@@ -1,8 +1,9 @@
# Copyright 2019-2024 Akretion France (http://www.akretion.com) # Copyright 2019 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models from odoo import fields, models
from odoo.tools import float_round
class AccountPayment(models.Model): class AccountPayment(models.Model):
@@ -10,24 +11,49 @@ class AccountPayment(models.Model):
sale_id = fields.Many2one('sale.order', string='Sale Order') sale_id = fields.Many2one('sale.order', string='Sale Order')
def _prepare_move_line_default_vals(self, write_off_line_vals=None): def action_validate_invoice_payment(self):
line_vals_list = super()._prepare_move_line_default_vals( if self.sale_id:
write_off_line_vals=write_off_line_vals) self.post()
# Add to the receivable/payable line else:
return super(AccountPayment, self).\
action_validate_invoice_payment()
def _get_counterpart_move_line_vals(self, invoice=False):
res = super(AccountPayment, self)._get_counterpart_move_line_vals(
invoice=invoice)
if self.sale_id:
res['sale_id'] = self.sale_id.id
return res
class AccountAbstractPayment(models.AbstractModel):
_inherit = "account.abstract.payment"
def default_get(self, fields_list):
res = super(AccountAbstractPayment, self).default_get(fields_list)
if ( if (
self.sale_id and self._context.get('active_model') == 'sale.order' and
len(line_vals_list) >= 2 and self._context.get('active_id')):
line_vals_list[1].get('account_id') == self.destination_account_id.id): so = self.env['sale.order'].browse(self._context['active_id'])
line_vals_list[1]['sale_id'] = self.sale_id.id res.update({
return line_vals_list 'amount': so.amount_total,
'currency_id': so.currency_id.id,
'payment_type': 'inbound',
'partner_id': so.partner_invoice_id.commercial_partner_id.id,
'partner_type': 'customer',
'communication': so.name,
'sale_id': so.id,
})
return res
def action_post(self): def _compute_payment_amount(self, invoices=None, currency=None):
super().action_post() amount = super(AccountAbstractPayment, self)._compute_payment_amount(
for pay in self: invoices=invoices, currency=currency)
if pay.sale_id and pay.payment_type == 'inbound': if self.sale_id:
pay._sale_down_payment_hook() payment_currency = currency
if not payment_currency:
def _sale_down_payment_hook(self): payment_currency = self.sale_id.currency_id
# can be used for notifications amount = float_round(
# WAS on account.move.line on v12 ; is on account.payment on v14 self.sale_id.amount_total - self.sale_id.amount_down_payment,
self.ensure_one() precision_rounding=payment_currency.rounding)
return amount

View File

@@ -1,4 +1,4 @@
# Copyright 2019-2024 Akretion France (http://www.akretion.com) # Copyright 2019 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -9,8 +9,8 @@ from odoo.tools import float_round
class SaleOrder(models.Model): class SaleOrder(models.Model):
_inherit = 'sale.order' _inherit = 'sale.order'
payment_ids = fields.One2many( payment_line_ids = fields.One2many(
'account.payment', 'sale_id', string='Advance Payments', 'account.move.line', 'sale_id', string='Advance Payments',
readonly=True) readonly=True)
amount_down_payment = fields.Monetary( amount_down_payment = fields.Monetary(
compute='_compute_amount_down_payment', string='Down Payment Amount') compute='_compute_amount_down_payment', string='Down Payment Amount')
@@ -19,20 +19,30 @@ class SaleOrder(models.Model):
compute='_compute_amount_down_payment', string='Residual') compute='_compute_amount_down_payment', string='Residual')
@api.depends( @api.depends(
'payment_ids.amount', 'payment_ids.currency_id', 'payment_ids.date', 'payment_line_ids.credit', 'payment_line_ids.debit',
'payment_ids.state', 'currency_id') 'payment_line_ids.amount_currency', 'payment_line_ids.currency_id',
'payment_line_ids.date', 'currency_id')
def _compute_amount_down_payment(self): def _compute_amount_down_payment(self):
for sale in self: for sale in self:
down_payment = 0.0 down_payment = 0.0
sale_currency = sale.currency_id sale_currency = sale.pricelist_id.currency_id
prec_rounding = sale_currency.rounding or 0.01 if sale_currency == sale.company_id.currency_id:
for payment in sale.payment_ids: for pl in sale.payment_line_ids:
if payment.payment_type == 'inbound' and payment.state == 'posted': down_payment -= pl.balance
down_payment += payment.currency_id._convert( else:
payment.amount, sale_currency, sale.company_id, for pl in sale.payment_line_ids:
payment.date) if (
pl.currency_id and
pl.currency_id == sale_currency and
pl.amount_currency):
down_payment -= pl.amount_currency
else:
down_payment -= sale.company_id.currency_id._convert(
pl.balance, sale_currency, sale.company_id,
pl.date)
down_payment = float_round( down_payment = float_round(
down_payment, precision_rounding=prec_rounding) down_payment, precision_rounding=sale.currency_id.rounding)
sale.amount_down_payment = down_payment sale.amount_down_payment = down_payment
sale.amount_residual = float_round( sale.amount_residual = float_round(
sale.amount_total - down_payment, precision_rounding=prec_rounding) sale.amount_total - down_payment,
precision_rounding=sale.currency_id.rounding)

View File

@@ -1,3 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_payment_register_sale,Full access on account.payment.register.sale,model_account_payment_register_sale,account.group_account_invoice,1,1,1,1
access_account_payment_sale_user,Full access on account.payment to sale user,account.model_account_payment,sales_team.group_sale_salesman,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_payment_register_sale Full access on account.payment.register.sale model_account_payment_register_sale account.group_account_invoice 1 1 1 1
3 access_account_payment_sale_user Full access on account.payment to sale user account.model_account_payment sales_team.group_sale_salesman 1 1 1 1

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright 2019-2024 Akretion France (http://www.akretion.com/) Copyright 2019 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
@@ -13,12 +13,10 @@
<field name="model">account.move.line</field> <field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_form"/> <field name="inherit_id" ref="account.view_move_line_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='blocked']/.." position="after"> <field name="invoice_id" position="after">
<group string="Sale Order" name="sale" attrs="{'invisible': [('account_internal_type', '!=', 'receivable')]}"> <field name="sale_id" attrs="{'invisible': [('account_internal_type', '!=', 'receivable')]}" domain="['|', ('partner_id', 'child_of', partner_id), ('partner_invoice_id', 'child_of', partner_id), ('state', '!=', 'cancel'), ('invoice_status', '!=', 'invoiced')]"/>
<field name="sale_id"/> <field name="account_internal_type" invisible="1"/>
<field name="account_internal_type" invisible="1"/> </field>
</group>
</xpath>
</field> </field>
</record> </record>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright 2018-2024 Akretion France (http://www.akretion.com/) Copyright 2018 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
@@ -8,13 +8,13 @@
<odoo> <odoo>
<record id="view_account_payment_form" model="ir.ui.view"> <record id="view_account_payment_sale_form" model="ir.ui.view">
<field name="name">account.payment.sale.form</field> <field name="name">account.payment.sale.form</field>
<field name="model">account.payment</field> <field name="model">account.payment</field>
<field name="inherit_id" ref="account.view_account_payment_form"/> <field name="inherit_id" ref="account.view_account_payment_invoice_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="destination_account_id" position="after"> <field name="invoice_ids" position="after">
<field name="sale_id" attrs="{'invisible': [('is_internal_transfer', '=', True)]}"/> <field name="sale_id" invisible="1"/>
</field> </field>
</field> </field>
</record> </record>

Some files were not shown because too many files have changed in this diff Show More