Compare commits

..

2 Commits

Author SHA1 Message Date
clementmbr
c7172a5c7f [IMP] add bool is_created_by_warehouse on locations 2024-05-27 20:40:51 -03:00
clementmbr
5023839119 [ADD] bew module stock_location_simple 2024-05-27 19:57:29 -03:00
70 changed files with 373 additions and 1116 deletions

View File

@@ -1,10 +1,10 @@
# Copyright 2017-2024 Akretion France (http://www.akretion.com/)
# Copyright 2017-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": "Bank Reconciliation Report",
"version": "14.0.2.0.0",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"author": "Akretion",
"website": "https://github.com/akretion/odoo-usability",

View File

@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-23 10:04+0000\n"
"PO-Revision-Date: 2024-10-23 10:04+0000\n"
"POT-Creation-Date: 2023-01-13 10:31+0000\n"
"PO-Revision-Date: 2023-01-13 10:31+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -17,7 +17,6 @@ msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Amount"
msgstr "Montant"
@@ -28,12 +27,6 @@ msgstr "Montant"
msgid "Balance %s:"
msgstr "Solde %s :"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Bank Balance:"
msgstr "Solde bancaire :"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__journal_ids
msgid "Bank Journals"
@@ -56,7 +49,7 @@ msgstr "Rapport de rapprochement bancaire"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model,name:account_bank_reconciliation_summary_xlsx.model_bank_reconciliation_report_wizard
msgid "Bank Reconciliation Report Wizard"
msgstr "Assistant de rapport de rapprochement bancaire"
msgstr "Assistant rapport de rapprochement bancaire"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.actions.report,name:account_bank_reconciliation_summary_xlsx.bank_reconciliation_xlsx
@@ -103,36 +96,17 @@ msgstr "Créé le"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Currency"
msgstr "Devise"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__date
#, python-format
msgid "Date"
msgstr "Date"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Description"
msgstr "Description"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Difference:"
msgstr "Écart :"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__display_name
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_report_bank_reconciliation_xlsx__display_name
msgid "Display Name"
msgstr "Nom affiché"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields.selection,name:account_bank_reconciliation_summary_xlsx.selection__bank_reconciliation_report_wizard__move_state__draft_posted
@@ -149,13 +123,13 @@ msgstr "Écritures"
#. module: account_bank_reconciliation_summary_xlsx
#: model_terms:ir.ui.view,arch_db:account_bank_reconciliation_summary_xlsx.bank_reconciliation_report_wizard_form
msgid "Export XLSX"
msgstr "Exporter en XLSX"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Generated from Odoo on %s by %s"
msgstr "Généré par Odoo le %s par %s"
msgid "Generated on %s"
msgstr "Généré le %s"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__id
@@ -169,18 +143,6 @@ msgstr ""
msgid "Journal"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Journal Entry"
msgstr "Pièce"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Justification:"
msgstr "Justification :"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
@@ -191,17 +153,23 @@ msgstr "Libellé"
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard____last_update
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_report_bank_reconciliation_xlsx____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Move Number"
msgstr "Numéro de pièce"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
@@ -211,23 +179,10 @@ msgstr "Aucun journal de banque sélectionné."
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "None"
msgstr "Aucun"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid ""
"On bank journal %(journal)s which is configured with currency "
"%(journal_currency)s, the account %(account)s must be configured with the "
"same currency (current account currency: %(account_currency)s)."
msgstr ""
"Sur le journal de banque %(journal)s qui est configuré avec la devise "
"%(journal_currency)s, le compte %(account)s doit être configuré avec la même"
" devise (devise actuelle du compte : %(account_currency)s)."
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
@@ -256,13 +211,3 @@ msgstr "Sous-total :"
#, python-format
msgid "TOTAL:"
msgstr "TOTAL :"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid ""
"The are %(count)s journal items in account %(account)s that have a currency "
"other than %(currency)s or where currency is not set."
msgstr ""
"Il y a %(count)s écritures comptables dans le compte %(account)s qui ont une"
" devise autre que %(currency)s ou dont la devise n'est pas définie."

View File

@@ -1,4 +1,4 @@
# Copyright 2017-2024 Akretion France (http://www.akretion.com/)
# Copyright 2017-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).
@@ -14,19 +14,32 @@ class BankReconciliationXlsx(models.AbstractModel):
_description = "Bank Reconciliation XLSX Report"
_inherit = "report.report_xlsx.abstract"
def _prepare_payment_move_lines(self, jdi, account, unreconciled_only=True):
domain = [
("company_id", "=", jdi['company'].id),
("account_id", "=", account.id),
("journal_id", "=", jdi['journal'].id),
("date", "<=", jdi['wizard'].date),
]
if jdi['wizard'].move_state == 'posted':
def _domain_add_move_state(self, wizard, domain):
if wizard.move_state == 'posted':
domain.append(('parent_state', '=', 'posted'))
elif jdi['wizard'].move_state == 'draft_posted':
elif wizard.move_state == 'draft_posted':
domain.append(('parent_state', 'in', ('draft', 'posted')))
def _get_account_balance(self, account, wizard):
domain = [
('account_id', '=', account.id),
('date', '<=', wizard.date),
('company_id', '=', wizard.company_id.id),
]
self._domain_add_move_state(wizard, domain)
res_rg = self.env['account.move.line'].read_group(domain, ['balance:sum'], [])
account_bal = res_rg and res_rg[0].get('balance', 0.0) or 0.0
return account_bal
def _prepare_payment_move_lines(self, journal, account, wizard, unreconciled_only=True):
domain = [
("company_id", "=", wizard.company_id.id),
("account_id", "=", account.id),
("journal_id", "=", journal.id),
("date", "<=", wizard.date),
]
if unreconciled_only:
limit_datetime_naive = datetime.combine(jdi['wizard'].date, datetime.max.time())
limit_datetime_naive = datetime.combine(wizard.date, datetime.max.time())
tz = pytz.timezone(self.env.user.tz)
limit_datetime_aware = tz.localize(limit_datetime_naive)
limit_datetime_utc = limit_datetime_aware.astimezone(pytz.utc)
@@ -34,6 +47,7 @@ class BankReconciliationXlsx(models.AbstractModel):
domain += [
'|', ('full_reconcile_id', '=', False),
('full_reconcile_id.create_date', '>', limit_datetime)]
self._domain_add_move_state(wizard, domain)
mlines = self.env["account.move.line"].search(domain)
res = []
for mline in mlines:
@@ -46,18 +60,13 @@ class BankReconciliationXlsx(models.AbstractModel):
):
cpart.append(line.account_id.code)
counterpart = " ,".join(cpart)
if jdi['currency'] == mline.currency_id:
amount = mline.amount_currency
else:
amount = mline.currency_id._convert(
mline.amount_currency, jdi['currency'], jdi['company'], mline.date)
res.append(
{
"date": mline.date,
"ref": move.ref or "",
"label": mline.name,
"partner": mline.partner_id.display_name or "",
"amount": amount,
"amount": mline.balance,
"move_name": move.name,
"counterpart": counterpart,
}
@@ -65,31 +74,27 @@ class BankReconciliationXlsx(models.AbstractModel):
return res
def _write_move_lines_block(self, jdi, row, account, add2total=True):
# Returns row
# For suspense lines, it may not add any cells if there are no suspense lines
# => in this case, it doesn't increment row
# If it adds cells, it returns row + 2 to add 2 empty rows at the end
sheet = jdi['sheet']
style = jdi['style']
style_suffix = not add2total and '_warn' or ''
subtotal = 0.0
mlines = self._prepare_payment_move_lines(jdi, account)
mlines = self._prepare_payment_move_lines(jdi['journal'], account, jdi['wizard'])
if mlines or add2total:
sheet.write(row, 0, '%s %s' % (account.name, account.code), style[f"title{style_suffix}"])
sheet.write(row, 1, "", style[f"title{style_suffix}"])
sheet.write(row, 0, '%s %s' % (account.name, account.code), style['title' + style_suffix])
sheet.write(row, 1, "", style['title' + style_suffix])
if not mlines:
if add2total:
sheet.write(row, 2, _("None"), style['none'])
else:
return row
return
else:
row += 1
col_labels = [
_("Date"),
_("Partner"),
_("Amount"),
_("Journal Entry"),
_("Move Number"),
_("Counter-part"),
_("Ref."),
_("Label"),
@@ -103,7 +108,7 @@ class BankReconciliationXlsx(models.AbstractModel):
for mline in mlines:
sheet.write(row, 0, mline["date"], style['regular_date'])
sheet.write(row, 1, mline["partner"], style['regular'])
sheet.write(row, 2, mline["amount"], style[jdi['currency']])
sheet.write(row, 2, mline["amount"], style['currency'])
sheet.write(row, 3, mline["move_name"], style['regular'])
sheet.write(row, 4, mline["counterpart"], style['regular'])
sheet.write(row, 5, mline["ref"], style['regular'])
@@ -113,20 +118,18 @@ class BankReconciliationXlsx(models.AbstractModel):
end_line = row
for col in range(1):
sheet.write(row, col, "", style[f"title{style_suffix}"])
sheet.write(row, 1, _("Sub-total:") + ' ', style[f"title_right{style_suffix}"])
sheet.write(row, col, "", style['title' + style_suffix])
sheet.write(row, 1, _("Sub-total:") + ' ', style['title_right' + style_suffix])
formula = f"=SUM({jdi['total_col']}{start_line}:{jdi['total_col']}{end_line})"
sheet.write_formula(row, 2, formula, style[f"{jdi['currency']}_bg{style_suffix}"], subtotal)
formula = '=SUM(%s%d:%s%d)' % (
jdi['total_col'], start_line, jdi['total_col'], end_line)
sheet.write_formula(row, 2, formula, style['currency_bg' + style_suffix], subtotal)
if add2total:
jdi['total'] += subtotal
jdi['total_formula'] += f"+{jdi['total_col']}{row + 1}"
return row + 2
jdi['total_formula'] += '+%s%d' % (jdi['total_col'], row + 1)
return row
def generate_xlsx_report(self, workbook, data, wizard):
lang = self.env.user.lang
self = self.with_context(lang=lang)
wizard = wizard.with_context(lang=lang)
if not wizard.journal_ids:
raise UserError(_("No bank journal selected."))
date_dt = wizard.date
@@ -134,21 +137,15 @@ class BankReconciliationXlsx(models.AbstractModel):
style = self._get_style(workbook, company)
move_state_label = dict(
wizard.fields_get('move_state', 'selection')['move_state']['selection'])
generated_on_label = _('Generated from Odoo on %s by %s') % (
format_datetime(self.env, datetime.utcnow()),
self.env.user.name)
generated_on_label = _('Generated on %s') % format_datetime(
self.env, datetime.utcnow())
for journal in wizard.journal_ids:
row = 0
sheet = workbook.add_worksheet(journal.code or journal.name)
bank_account = journal.default_account_id
jdi = {
'wizard': wizard,
'company': company,
'journal': journal,
'currency': journal.currency_id or company.currency_id,
'bank_account': bank_account,
'style': style,
'workbook': workbook,
'sheet': sheet,
'total': 0.0,
'total_formula': '=',
@@ -172,7 +169,7 @@ class BankReconciliationXlsx(models.AbstractModel):
sheet.set_column(6, 6, 60)
row += 3
sheet.write(row, 0, _("Company"), style['wizard_field'])
sheet.write(row, 1, company.display_name, style['wizard_value'])
sheet.write(row, 1, wizard.company_id.display_name, style['wizard_value'])
row += 1
sheet.write(row, 0, _("Date"), style['wizard_field'])
sheet.write(row, 1, date_dt, style['wizard_value_date'])
@@ -180,121 +177,51 @@ class BankReconciliationXlsx(models.AbstractModel):
sheet.write(row, 0, _("Journal"), style['wizard_field'])
sheet.write(row, 1, journal.display_name, style['wizard_value'])
row += 1
sheet.write(row, 0, _("Currency"), style['wizard_field'])
sheet.write(row, 1, jdi['currency'].name, style['wizard_value'])
row += 1
sheet.write(row, 0, _("Entries"), style['wizard_field'])
sheet.write(row, 1, move_state_label[wizard.move_state], style['wizard_value'])
# Setup check
if journal.currency_id and journal.currency_id != company.currency_id:
if journal.currency_id != bank_account.currency_id:
raise UserError(_(
"On bank journal %(journal)s which is configured with currency "
"%(journal_currency)s, the account %(account)s must be configured "
"with the same currency (current account currency: %(account_currency)s).",
journal=journal.display_name,
journal_currency=journal.currency_id.name,
account=bank_account.display_name,
account_currency=bank_account.currency_id.name or _('None')))
bad_line_count = self.env['account.move.line'].search_count([
('company_id', '=', company.id),
('journal_id', '=', journal.id),
('account_id', '=', bank_account.id),
('currency_id', '!=', jdi['currency'].id),
])
if bad_line_count:
raise UserError(_(
"The are %(count)s journal items in account %(account)s "
"that have a currency other than %(currency)s or where "
"currency is not set.",
count=bad_line_count,
account=bank_account.display_name,
currency=jdi['currency'].name))
# 1) Show balance of bank account
row += 3
bank_account = journal.default_account_id
for col in range(1):
sheet.write(row, col, "", style['title'])
sheet.write(row, 1, _("Balance %s:") % bank_account.code + ' ', style['title_right'])
if wizard.move_state == 'posted':
domain = [('parent_state', '=', 'posted')]
else:
# by default, the native method _get_journal_bank_account_balance()
# has ('parent_state', '!=', 'cancel')
domain = None
account_bal, nb_lines = journal._get_journal_bank_account_balance(domain=domain)
account_bal = self._get_account_balance(bank_account, wizard)
sheet.write(row, 2, account_bal, style[f"{jdi['currency']}_bg"])
sheet.write(row, 2, account_bal, style['currency_bg'])
jdi['total'] += account_bal
jdi['total_formula'] += f"{jdi['total_col']}{row + 1}"
jdi['total_formula'] += '%s%d' % (jdi['total_col'], row + 1)
row += 2
# 2) Show payment lines IN (debit)
debit_account = journal.payment_debit_account_id
row = self._write_move_lines_block(jdi, row, debit_account)
row += 2
# 3) Show payment lines OUT (credit)
credit_account = journal.payment_credit_account_id
row = self._write_move_lines_block(jdi, row, credit_account)
row += 2
for col in range(1):
sheet.write(row, col, "", style['title'])
sheet.write(row, 1, _("TOTAL:") + ' ', style['title_right'])
sheet.write_formula(
row, 2, jdi['total_formula'], style[f"{jdi['currency']}_bg"], jdi['total'])
total_row = row
row += 2
row, 2, jdi['total_formula'], style['currency_bg'], jdi['total'])
row += 3
# 4) Show suspense account lines
row = self._write_move_lines_block(
jdi, row, journal.suspense_account_id, add2total=False)
# Static cells
for col in range(1):
sheet.write(row, col, "", style['title'])
sheet.write(row, 1, _("Bank Balance:") + ' ', style['title_right'])
sheet.write(row, 2, 0, style[f"{jdi['currency']}_bg_manual"])
bank_bal_row = row
row += 2
for col in range(1):
sheet.write(row, col, "", style['title'])
sheet.write(row, 1, _("Difference:") + ' ', style['title_right'])
sheet.write_formula(
row, 2, f"={jdi['total_col']}{total_row + 1}-{jdi['total_col']}{bank_bal_row + 1}",
style[f"{jdi['currency']}_bg"], jdi['total'])
row += 2
for col in range(1):
sheet.write(row, col, "", style['title'])
sheet.write(row, 1, _("Justification:") + ' ', style['title_right'])
justif_lines = 6
sheet.write_formula(
row, 2, f"=SUM({jdi['total_col']}{row+3}:{jdi['total_col']}{row+3+justif_lines-1})",
style[f"{jdi['currency']}_bg"], 0)
row += 1
col_labels = [
_("Date"),
_("Description"),
_("Amount"),
]
col = 0
for col_label in col_labels:
sheet.write(row, col, col_label, style['col_header'])
col += 1
for x in range(justif_lines):
row += 1
sheet.write(row, 0, "", style['regular_date'])
sheet.write(row, 1, "", style['regular'])
sheet.write(row, 2, "", style[jdi['currency']])
def _get_style(self, workbook, company):
style = {}
font_size = 10
light_grey = "#eeeeee"
light_blue = "#e0edff"
title_blue = "#e6e6fa"
subtotal_orange = "#ffcc00"
subtotal_warn = "#ffff99"
amount_manual = "#ffeeab"
title_warn = "#ff9999"
subtotal_warn = "#ffff99"
light_purple = "#ffdeff"
lang_code = self.env.user.lang
lang = False
if lang_code:
@@ -322,7 +249,7 @@ class BankReconciliationXlsx(models.AbstractModel):
)
title_style = {
"bold": True,
"bg_color": light_blue,
"bg_color": title_blue,
"font_size": font_size,
"align": "left",
}
@@ -330,7 +257,7 @@ class BankReconciliationXlsx(models.AbstractModel):
style['title'] = workbook.add_format(dict(title_style))
style['wizard_field'] = workbook.add_format(dict(title_style, bg_color=light_grey))
wizard_value_style = {
"bg_color": light_blue,
"bg_color": light_purple,
"bold": True,
"font_size": font_size,
"align": "left",
@@ -347,29 +274,23 @@ class BankReconciliationXlsx(models.AbstractModel):
dict(title_style, align="left", bg_color=title_warn))
style['title_right_warn'] = workbook.add_format(
dict(title_style, align="right", bg_color=title_warn))
style['regular'] = workbook.add_format({"font_size": font_size, "border": 1})
style['regular'] = workbook.add_format({"font_size": font_size})
if "%" in xls_date_format:
# fallback
xls_date_format = "yyyy-mm-dd"
style['regular_date'] = workbook.add_format(
{"num_format": xls_date_format, "font_size": font_size, "align": "left", "border": 1}
{"num_format": xls_date_format, "font_size": font_size, "align": "left"}
)
for currency in self.env['res.currency'].search([]):
symbol = currency.symbol or currency.name
decimals = '0' * currency.decimal_places
if currency.position == 'before':
cur_format = f"{symbol} #,##0.{decimals}"
else:
cur_format = f"#,##0.{decimals} {symbol}"
# It seems that Excel replaces automatically the decimal
# and thousand separator by those of the language under which
# Excel runs
currency_style = {"num_format": cur_format, "font_size": font_size}
style[currency] = workbook.add_format(dict(currency_style, border=1))
style[f'{currency}_bg'] = workbook.add_format(
dict(currency_style, bg_color=subtotal_orange))
style[f'{currency}_bg_warn'] = workbook.add_format(
dict(currency_style, bg_color=subtotal_warn))
style[f'{currency}_bg_manual'] = workbook.add_format(
dict(currency_style, bg_color=amount_manual))
cur_format = "#,##0.00 %s" % (
company.currency_id.symbol or company.currency_id.name
)
# It seems that Excel replaces automatically the decimal
# and thousand separator by those of the language under which
# Excel runs
currency_style = {"num_format": cur_format, "font_size": font_size}
style['currency'] = workbook.add_format(currency_style)
style['currency_bg'] = workbook.add_format(
dict(currency_style, bg_color=subtotal_orange))
style['currency_bg_warn'] = workbook.add_format(
dict(currency_style, bg_color=subtotal_warn))
return style

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017-2024 Akretion France (http://www.akretion.com/)
Copyright 2017-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).
-->
@@ -12,6 +12,7 @@
<field name="report_type">xlsx</field>
<field name="report_name">bank.reconciliation.xlsx</field>
<field name="report_file">bank.reconciliation.xlsx</field>
<!-- print_report_name doesn't work here... -->
<field name="print_report_name">'bank_reconciliation-%s' % (object.date)</field>
</record>
</odoo>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017-2024 Akretion France (http://www.akretion.com/)
Copyright 2017-2020 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).
-->

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018-2024 Akretion France (http://www.akretion.com/)
Copyright 2018-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).
-->
@@ -9,7 +9,7 @@
<record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
<field
name="name"
>bank_reconciliation_summary.account_journal_dashboard</field>
>bank_reconciliation_summarry.account_journal_dashboard</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.account_journal_dashboard_kanban_view" />
<field name="arch" type="xml">

View File

@@ -1,4 +1,4 @@
# Copyright 2017-2024 Akretion France (http://www.akretion.com/)
# Copyright 2017-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).

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017-2024 Akretion France (http://www.akretion.com/)
Copyright 2017-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).
-->

View File

@@ -227,12 +227,6 @@ class AccountMoveUpdate(models.TransientModel):
inv.message_post(body=_(
'Non-legal fields of invoice updated via the Invoice Update '
'wizard.'))
# Purge existing PDF
report = self.env.ref("account.account_invoices")
attachment = report.retrieve_attachment(inv)
# attachment may be None
if attachment:
attachment.unlink()
return True

View File

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

View File

@@ -1,25 +0,0 @@
# 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).
{
'name': 'Account Payment Line Manual Account',
'version': '14.0.1.0.0',
'category': 'Accounting',
'license': 'AGPL-3',
'summary': 'Ability to select the account on payment lines without journal item',
'description': """
With this module, when you manually create a payment line that is not linked to a journal item, you can select an account (by default, it is set to the payable/receivable account of the partner) : this account will be used as the counter part of the outbound/inbound payment account configured on the bank journal. It covers special needs of a few companies that use SEPA credit transfer for the same partner in different accounting scenarios.
This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'maintainers': ['alexis-via'],
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['account_payment_order'],
'data': [
"views/account_payment_line.xml",
],
'installable': True,
}

View File

@@ -1,2 +0,0 @@
from . import account_payment_line
from . import account_payment_order

View File

@@ -1,38 +0,0 @@
# Copyright 2024 Akretion France (https://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class AccountPaymentLine(models.Model):
_inherit = "account.payment.line"
account_id = fields.Many2one(
'account.account',
compute="_compute_account_id", store=True, readonly=False, check_company=True,
domain="[('company_id', '=', company_id), ('deprecated', '=', False)]")
analytic_account_id = fields.Many2one(
'account.analytic.account', string='Analytic Account',
domain="[('company_id', 'in', [False, company_id])]",
check_company=True)
@api.depends('move_line_id', 'partner_id')
def _compute_account_id(self):
for line in self:
account_id = False
if not line.move_line_id and line.partner_id:
partner = line.partner_id.with_company(line.order_id.company_id.id)
if line.order_id.payment_type == "inbound":
account_id = partner.property_account_receivable_id.id
else:
account_id = partner.property_account_payable_id.id
line.account_id = account_id
# take info account account_id for grouping
def payment_line_hashcode(self):
hashcode = super().payment_line_hashcode()
account_str = str(self.account_id.id or False)
analytic_account_str = str(self.analytic_account_id.id or False)
hashcode = '-'.join([hashcode, account_str, analytic_account_str])
return hashcode

View File

@@ -1,18 +0,0 @@
# Copyright 2024 Akretion France (https://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
class AccountPaymentOrder(models.Model):
_inherit = "account.payment.order"
def _prepare_move_line_partner_account(self, bank_line):
vals = super()._prepare_move_line_partner_account(bank_line)
if not bank_line.payment_line_ids[0].move_line_id:
vals.update({
'account_id': bank_line.payment_line_ids[0].account_id.id,
'analytic_account_id': bank_line.payment_line_ids[0].analytic_account_id.id or False,
})
return vals

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Akretion France (https://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="account_payment_line_form" model="ir.ui.view">
<field name="model">account.payment.line</field>
<field name="inherit_id" ref="account_payment_order.account_payment_line_form"/>
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="account_id" attrs="{'invisible': [('move_line_id', '!=', False)], 'required': [('move_line_id', '=', False)]}"/>
<field name="analytic_account_id" attrs="{'invisible': [('move_line_id', '!=', False)]}" groups="analytic.group_analytic_accounting"/>
</field>
</field>
</record>
<record id="account_payment_line_tree" model="ir.ui.view">
<field name="model">account.payment.line</field>
<field name="inherit_id" ref="account_payment_order.account_payment_line_tree"/>
<field name="arch" type="xml">
<field name="move_line_id" position="after">
<field name="account_id" optional="hide"/>
<field name="analytic_account_id" optional="hide" groups="analytic.group_analytic_accounting"/>
</field>
</field>
</record>
</odoo>

View File

@@ -43,6 +43,13 @@ class AccountBankStatement(models.Model):
res.append((statement.id, name))
return res
def button_reopen(self):
self = self.with_context(skip_undo_reconciliation=True)
return super().button_reopen()
def button_undo_reconciliation(self):
self.line_ids.button_undo_reconciliation()
class AccountBankStatementLine(models.Model):
_inherit = 'account.bank.statement.line'
@@ -89,3 +96,9 @@ class AccountBankStatementLine(models.Model):
'res_id': self.move_id.id,
})
return action
def button_undo_reconciliation(self):
if self._context.get("skip_undo_reconciliation"):
return
else:
return super().button_undo_reconciliation()

View File

@@ -10,8 +10,6 @@ from odoo.exceptions import UserError, ValidationError
from odoo.osv import expression
from odoo.tools import float_is_zero
from odoo.tools.misc import format_date
from odoo.tools.safe_eval import safe_eval, time
from collections import defaultdict
_logger = logging.getLogger(__name__)
@@ -52,39 +50,6 @@ class AccountMove(models.Model):
string="Dispute",
tracking=True,
)
# Having amounts in invoice currency can be useful in tree view of invoices
# We add those fields with optional="hide"
amount_untaxed_invoice_currency_signed = fields.Monetary(
compute="_compute_amount_invoice_currency_signed", store=True,
string="Untaxed Amount Invoice Currency Signed")
amount_tax_invoice_currency_signed = fields.Monetary(
compute="_compute_amount_invoice_currency_signed", store=True,
string="Tax Invoice Currency Signed")
amount_total_invoice_currency_signed = fields.Monetary(
compute="_compute_amount_invoice_currency_signed", store=True,
string="Total Invoice Currency Signed")
amount_residual_invoice_currency_signed = fields.Monetary(
compute="_compute_amount_invoice_currency_signed", store=True,
string="Amount Due Invoice Currency Signed")
# Field search_account_id is just for search view
search_account_id = fields.Many2one(related='line_ids.account_id')
@api.depends('amount_untaxed', 'amount_tax', 'amount_total', 'amount_residual', 'move_type')
def _compute_amount_invoice_currency_signed(self):
for move in self:
amount_untaxed_invoice_currency_signed = move.amount_untaxed
amount_tax_invoice_currency_signed = move.amount_tax
amount_total_invoice_currency_signed = move.amount_total
amount_residual_invoice_currency_signed = move.amount_residual
if move.move_type in ('out_refund', 'in_refund'):
amount_untaxed_invoice_currency_signed *= -1
amount_tax_invoice_currency_signed *= -1
amount_total_invoice_currency_signed *= -1
amount_residual_invoice_currency_signed *= -1
move.amount_untaxed_invoice_currency_signed = amount_untaxed_invoice_currency_signed
move.amount_tax_invoice_currency_signed = amount_tax_invoice_currency_signed
move.amount_total_invoice_currency_signed = amount_total_invoice_currency_signed
move.amount_residual_invoice_currency_signed = amount_residual_invoice_currency_signed
@api.depends("line_ids", "line_ids.blocked")
def _compute_blocked(self):
@@ -258,14 +223,33 @@ class AccountMove(models.Model):
move.suitable_journal_ids = self.env['account.journal'].search(domain)
def button_draft(self):
# Delete attached pdf invoice
for move in self.filtered(lambda x: x.move_type in ('out_invoice', 'out_refund')):
for report_xmlid in ('account.account_invoices', 'account.account_invoices_without_payment'):
report = self.env.ref(report_xmlid)
attach = report.retrieve_attachment(move)
if attach:
attach.unlink()
super().button_draft()
# Delete attached pdf invoice
try:
report_invoice = self.env['ir.actions.report']._get_report_from_name('account.report_invoice')
except IndexError:
report_invoice = False
if report_invoice and report_invoice.attachment:
for move in self.filtered(lambda x: x.move_type in ('out_invoice', 'out_refund')):
# The pb is that the filename is dynamic and related to move.state
# in v12, the feature was native and they used that kind of code:
# with invoice.env.do_in_draft():
# invoice.number, invoice.state = invoice.move_name, 'open'
# attachment = self.env.ref('account.account_invoices').retrieve_attachment(invoice)
# But do_in_draft() doesn't exists in v14
# If you know how we could do that, please update the code below
attachment = self.env['ir.attachment'].search([
('name', '=', self._get_invoice_attachment_name()),
('res_id', '=', move.id),
('res_model', '=', self._name),
('type', '=', 'binary'),
], limit=1)
if attachment:
attachment.unlink()
def _get_invoice_attachment_name(self):
self.ensure_one()
return '%s.pdf' % (self.name and self.name.replace('/', '_') or 'INV')
def _get_accounting_date(self, invoice_date, has_tax):
# On vendor bills/refunds, we want date = invoice_date unless

View File

@@ -16,6 +16,14 @@
<button name="button_reopen" position="attributes">
<attribute name="confirm">Are you sure ? Don't do 'Reset to New' if you just want to modify the bank journal entry of an existing statement line.</attribute>
</button>
<button name="button_reopen" position="after">
<button
name="button_undo_reconciliation"
type="object"
confirm="Are you sure to unreconcile all the entries of the bank statement?"
states="open"
string="Unreconcile All"/>
</button>
<xpath expr="//field[@name='line_ids']/tree/button[@name='button_undo_reconciliation']" position="after">
<field name="move_id" invisible="1"/>
<button name="show_account_move" type="object"

View File

@@ -67,14 +67,6 @@
<field name="amount_residual_signed" position="attributes">
<attribute name="optional">show</attribute>
</field>
<field name="amount_untaxed_signed" position="before">
<!-- No sum="1" on the invoice currency fields, because it doesn't make sense
to add amounts in different currencies -->
<field name="amount_untaxed_invoice_currency_signed" string="Tax Excluded Inv. Cur." optional="hide"/>
<field name="amount_tax_invoice_currency_signed" string="Tax Inv. Cur." optional="hide"/>
<field name="amount_total_invoice_currency_signed" string="Total Inv. Cur." optional="hide"/>
<field name="amount_residual_invoice_currency_signed" string="Amount Due Inv. Cur." optional="hide"/>
</field>
</field>
</record>
@@ -101,16 +93,6 @@
</field>
</record>
<record id="view_account_move_filter" model="ir.ui.view">
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_move_filter"/>
<field name="arch" type="xml">
<field name="journal_id" position="after">
<field name="search_account_id"/>
</field>
</field>
</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"/>

View File

@@ -21,12 +21,10 @@ class AccountMoveReversal(models.TransientModel):
moves = amo.browse(self._context['active_ids'])
if len(moves) == 1 and moves.move_type not in ('out_invoice', 'in_invoice'):
res['date'] = moves.date + relativedelta(days=1)
entry_moves = moves.filtered(lambda m: m.move_type == "entry")
if entry_moves:
reversed_move = amo.search([('reversed_entry_id', 'in', entry_moves.ids)], limit=1)
if reversed_move:
raise UserError(_(
"Move '%s' has already been reversed by move '%s'.") % (
reversed_move.reversed_entry_id.display_name,
reversed_move.display_name))
reversed_move = amo.search([('reversed_entry_id', 'in', moves.ids)], limit=1)
if reversed_move:
raise UserError(_(
"Move '%s' has already been reversed by move '%s'.") % (
reversed_move.reversed_entry_id.display_name,
reversed_move.display_name))
return res

View File

@@ -1,13 +0,0 @@
diff --git a/odoo/tools/mimetypes.py b/odoo/tools/mimetypes.py
index d104198a4ae..6eeabcc63a3 100644
--- a/odoo/tools/mimetypes.py
+++ b/odoo/tools/mimetypes.py
@@ -123,7 +123,7 @@ _mime_mappings = (
_Entry('image/png', [b'\x89PNG\r\n\x1A\n'], []),
_Entry('image/gif', [b'GIF87a', b'GIF89a'], []),
_Entry('image/bmp', [b'BM'], []),
- _Entry('image/svg+xml', [b'<'], [
+ _Entry('application/xml', [b'<'], [
_check_svg,
]),
_Entry('image/x-icon', [b'\x00\x00\x01\x00'], []),

View File

@@ -69,10 +69,6 @@ class ResCompany(models.Model):
'value': hasattr(self, 'siren') and self.siren or False,
'label': _('SIREN:'),
},
'rcs_siren': {
'value': hasattr(self, 'siren') and self.siren and self.company_registry and f"{self.company_registry} {self.siren}",
'label': 'RCS',
},
'eori': {
'value': self._get_eori(),
'label': _('EORI:'),
@@ -106,7 +102,7 @@ class ResCompany(models.Model):
"""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', 'rcs_siren', 'capital'], ['vat', 'siret', 'eori', 'ape']]
line_details = [['phone', 'website', 'capital'], ['vat', 'siret', 'eori', 'ape']]
return line_details
# for reports

View File

@@ -1,16 +0,0 @@
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index e4c1b187169..ccd8110478f 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -516,7 +516,10 @@ var FormRenderer = BasicRenderer.extend({
* @returns {integer}
*/
_renderButtonBoxNbButtons: function () {
- return [2, 2, 2, 4][config.device.size_class] || 7;
+ /* AKRETION HACK 24/04/2024
+ * show 14 buttons before adding 'More' dropdown list (instead of 7 by default
+ */
+ return [2, 2, 2, 4][config.device.size_class] || 14;
},
/**
* Do not render a field widget if it is always invisible.

View File

@@ -22,7 +22,6 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
'depends': ['delivery'],
'data': [
'views/stock_picking.xml',
'views/product_packaging.xml',
],
'installable': True,
}

View File

@@ -1,4 +1 @@
from . import product_packaging
from . import stock_picking
from . import stock_move
from . import stock_quant_package

View File

@@ -1,31 +0,0 @@
# Copyright 2018-2021 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 ProductPackaging(models.Model):
_inherit = 'product.packaging'
# product.packaging is defined in the 'product' module and enhanced in the 'delivery' module
# I used to make the improvements on the datamodel of product.packaging in the OCA module
# 'stock_packaging_usability_pp' from OCA/stock-logistics-tracking,
# but I eventually figured out that the feature provided by 'stock_packaging_usability_pp'
# was native in the 'delivery' module via the wizard choose.delivery.package.
# So I stopped using 'stock_packaging_usability_pp' and I moved the datamodel changes
# here in the module 'delivery_usability'
name = fields.Char(translate=True)
weight = fields.Float(digits="Stock Weight", string="Empty Package Weight")
active = fields.Boolean(default=True)
# packaging_type is important, in particular for pallets for which
# we need a special implementation to enter the height
packaging_type = fields.Selection(
[
("unit", "Unit"),
("pack", "Pack"),
("box", "Box"),
("pallet", "Pallet"),
],
string="Type",
)

View File

@@ -1,23 +0,0 @@
# Copyright 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, models
class StockMove(models.Model):
_inherit = "stock.move"
# Fixing bug https://github.com/odoo/odoo/issues/34702
@api.depends("product_id", "product_uom_qty", "product_uom")
def _cal_move_weight(self):
weight_uom_categ = self.env.ref("uom.product_uom_categ_kgm")
kg_uom = self.env.ref("uom.product_uom_kgm")
for move in self:
if move.product_id.uom_id.category_id == weight_uom_categ:
move.weight = move.product_id.uom_id._compute_quantity(
move.product_qty, kg_uom
)
else:
move.weight = move.product_qty * move.product_id.weight

View File

@@ -1,68 +0,0 @@
# Copyright 2019-2024 Akretion France (https://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
from odoo.tools import float_is_zero
class StockQuantPackage(models.Model):
_inherit = "stock.quant.package"
# These 2 fields are defined in the 'delivery' module but they forgot
# the decimal precision
shipping_weight = fields.Float(digits="Stock Weight")
weight = fields.Float(digits="Stock Weight")
# Fixing bug https://github.com/odoo/odoo/issues/34702
# and take into account the weight of the packaging
# WARNING: this method _compute_weight() is also inherited by the OCA module
# base_delivery_carrier_label so if you use that module, you should copy
# that piece of code in a custom module that depend on delivery_usability
# and base_delivery_carrier_label
def _compute_weight(self):
smlo = self.env["stock.move.line"]
weight_uom_categ = self.env.ref("uom.product_uom_categ_kgm")
kg_uom = self.env.ref("uom.product_uom_kgm")
weight_prec = self.env['decimal.precision'].precision_get('Stock Weight')
for package in self:
# if the weight of the package has been measured,
# it is written in shipping_weight
if not float_is_zero(package.shipping_weight, precision_digits=weight_prec):
weight = package.shipping_weight
# otherwise, we compute the theorical weight from the weight of the products
# and the weight of the packaging
# Since Odoo v11, consu products don't create quants, so I can't loop
# on pack.quant_ids to get all the items inside a package: I have to
# get the picking, then loop on the stock.move.line of that picking
# linked to that package
else:
weight = 0.0
# the package can be seen in a return
# So I get the picking of it's first appearance
domain = [
("result_package_id", "=", package.id),
("product_id", "!=", False),
]
first_move_line = smlo.search(
domain + [('picking_id', '!=', False)], limit=1, order='id')
if first_move_line:
picking_id = first_move_line.picking_id.id
current_picking_move_line_ids = smlo.search(
domain + [("picking_id", "=", picking_id)])
for ml in current_picking_move_line_ids:
if ml.product_uom_id.category_id == weight_uom_categ:
weight += ml.product_uom_id._compute_quantity(
ml.qty_done, kg_uom
)
else:
weight += (
ml.product_uom_id._compute_quantity(
ml.qty_done, ml.product_id.uom_id
)
* ml.product_id.weight
)
if package.packaging_id:
weight += package.packaging_id.weight
package.weight = weight

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2019-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>
<!-- I don't know why the form view of product.packaging in the delivery
module has "<field name="inherit_id" eval="False"/>"
instead of a standard inherit of product.product_packaging_form_view -->
<record id="product_packaging_delivery_form" model="ir.ui.view">
<field name="name">stock_packaging_usability_pp.product.packaging.form</field>
<field name="model">product.packaging</field>
<field name="inherit_id" ref="delivery.product_packaging_delivery_form" />
<field name="arch" type="xml">
<field name="package_carrier_type" position="after">
<field name="packaging_type" />
<field name="active" invisible="1" />
</field>
<label for="max_weight" position="before">
<label for="weight" />
<div class="o_row" name="weight">
<field name="weight" />
<span><field name="weight_uom_name" /></span>
</div>
</label>
<label for="name" position="before">
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
</label>
</field>
</record>
<record id="product_packaging_delivery_tree" model="ir.ui.view">
<field name="name">stock_packaging_usability_pp.product.packaging.tree</field>
<field name="model">product.packaging</field>
<field name="inherit_id" ref="delivery.product_packaging_delivery_tree" />
<field name="arch" type="xml">
<field name="name" position="after">
<field name="packaging_type" optional="show" />
</field>
<field name="max_weight" position="before">
<field name="weight" optional="show" />
</field>
</field>
</record>
<!-- There is no native serch view for product.packaging -->
<record id="product_packaging_search" model="ir.ui.view">
<field name="name">product.packaging.search</field>
<field name="model">product.packaging</field>
<field name="arch" type="xml">
<search>
<field name="name" />
<separator />
<filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<group name="groupby">
<filter
name="packaging_type_groupby"
string="Packaging Type"
context="{'group_by': 'packaging_type'}"
/>
</group>
</search>
</field>
</record>
</odoo>

View File

@@ -20,10 +20,10 @@ This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'website': 'http://www.akretion.com',
'depends': ['mass_mailing', 'link_tracker_usability'],
'data': [
'views/link_tracker.xml',
# 'views/link_tracker.xml',
],
'installable': True,
'installable': False,
}

View File

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

View File

@@ -1,14 +0,0 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class MassMailing(models.Model):
_inherit = 'mailing.mailing'
def action_view_clicked(self):
action = super().action_view_clicked()
action["view_mode"] = "tree,form"
return action

View File

@@ -8,13 +8,39 @@
<odoo>
<record id="link_tracker_click_view_tree" model="ir.ui.view">
<record id="view_link_tracker_click_tree" model="ir.ui.view">
<field name="name">mm.usability.link.tracker.click.tree</field>
<field name="model">link.tracker.click</field>
<field name="inherit_id" ref="mass_mailing.link_tracker_click_view_tree"/>
<field name="inherit_id" ref="link_tracker.view_link_tracker_click_tree"/>
<field name="arch" type="xml">
<field name="mass_mailing_id" position="after">
<field name="mailing_trace_id" optional="hide"/>
<field name="country_id" position="after">
<field name="mass_mailing_id"/>
<field name="mail_stat_recipient"/>
</field>
</field>
</record>
<record id="view_link_tracker_click_form" model="ir.ui.view">
<field name="name">mm.usability.link.tracker.click.form</field>
<field name="model">link.tracker.click</field>
<field name="inherit_id" ref="link_tracker.view_link_tracker_click_form"/>
<field name="arch" type="xml">
<field name="country_id" position="after">
<field name="mass_mailing_id"/>
<field name="mass_mailing_campaign_id"/>
<field name="mail_stat_id"/>
<field name="mail_stat_recipient"/>
</field>
</field>
</record>
<record id="link_tracker_click_search" model="ir.ui.view">
<field name="name">mm.usability.link.tracker.click.search</field>
<field name="model">link.tracker.click</field>
<field name="inherit_id" ref="link_tracker_usability.link_tracker_click_search"/>
<field name="arch" type="xml">
<field name="link_id" position="after">
<field name="mail_stat_recipient"/>
</field>
</field>
</record>

View File

@@ -22,7 +22,7 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
""",
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['mrp_account'],
'depends': ['mrp'],
'data': [
'security/mrp_average_cost_security.xml',
'security/ir.model.access.csv',

View File

@@ -165,9 +165,7 @@ class MrpProduction(models.Model):
company_currency_id = fields.Many2one(
related='company_id.currency_id', string='Company Currency')
# extra_cost is a native field of mrp_account
# we convert it to a computed field
# extra_cost is per unit in the UoM of mrp.production (product_uom_id)
# extra_cost is per unit in the UoM of the mrp.production (product_uom_id)
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 "
@@ -180,7 +178,7 @@ class MrpProduction(models.Model):
def _compute_extra_cost(self):
for prod in self:
bom = prod.bom_id
if bom and bom.type == 'normal' and bom.product_uom_id.category_id == prod.product_uom_id.category_id:
if bom and bom.type == 'normal':
extra_cost_bom_qty_uom = bom.extra_cost + bom.total_labour_cost
extra_cost_per_unit_in_prod_uom = 0
qty_prod_uom = bom.product_uom_id._compute_quantity(bom.product_qty, prod.product_uom_id)

View File

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

View File

@@ -10,11 +10,9 @@
'summary': 'Usability improvements on mrp_subcontracting',
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['mrp_subcontracting', 'stock_usability'],
'depends': ['mrp_subcontracting'],
'data': [
'views/mrp_bom.xml',
'views/stock_move.xml',
'views/stock_picking.xml',
],
'installable': True,
}

View File

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

View File

@@ -1,21 +0,0 @@
# Copyright 2024 Akretion France (https://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class StockMove(models.Model):
_inherit = 'stock.move'
subcontracting_production_id = fields.Many2one(
'mrp.production',
compute='_compute_subcontracting_production_id',
)
def _compute_subcontracting_production_id(self):
for move in self:
subcontracting_production_id = False
if move.is_subcontract and move.move_orig_ids.production_id:
subcontracting_production_id = move.move_orig_ids.production_id[-1:].id
move.subcontracting_production_id = subcontracting_production_id

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Akretion France (http://www.akretion.com/)
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).
-->

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Akretion France (https://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="model">stock.move</field>
<field name="inherit_id" ref="stock.view_move_form"/>
<field name="arch" type="xml">
<group name="advanced" position="inside">
<field name="is_subcontract" readonly="1" attrs="{'invisible': [('is_subcontract', '=', False)]}" groups="mrp.group_mrp_manager"/>
<field name="subcontracting_production_id" attrs="{'invisible': [('is_subcontract', '=', False)]}" groups="mrp.group_mrp_manager"/>
</group>
</field>
</record>
</odoo>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Akretion France (https://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">
<xpath expr="//field[@name='move_ids_without_package']/form/group" position="inside">
<field name="is_subcontract" readonly="1" attrs="{'invisible': [('is_subcontract', '=', False)]}" groups="mrp.group_mrp_manager"/>
<field name="subcontracting_production_id" attrs="{'invisible': [('is_subcontract', '=', False)]}" groups="mrp.group_mrp_manager"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -19,10 +19,6 @@ class MrpProduction(models.Model):
}, tracking=True)
# Add field product_categ_id for reporting only
product_categ_id = fields.Many2one(related='product_id.categ_id', store=True)
lot_producing_id = fields.Many2one(tracking=True)
location_src_id = fields.Many2one(tracking=True)
location_dest_id = fields.Many2one(tracking=True)
bom_id = fields.Many2one(tracking=True)
# Method used by the report, inherited in this module
@api.model

View File

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

View File

@@ -1,27 +0,0 @@
# Copyright 2014-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).
{
'name': 'Partner Product Shortcut',
'version': '14.0.1.0.0',
'category': 'Contact Management',
'license': 'AGPL-3',
'summary': 'Adds a shortcut on partner form to the products supplied by this partner',
'description': """
Partner Product Shortcut
========================
Adds a smartbutton on partner form to the products supplied by this partner.
This is an alternative to the OCA module `partner_supplierinfo_smartbutton <https://github.com/OCA/purchase-workflow/tree/14.0/partner_supplierinfo_smartbutton>`_ which adds a smartbutton on partner form to links to the related product.supplierinfo (and not to product.template like in this module).
This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['product'],
'data': ['res_partner_view.xml'],
'installable': True,
}

View File

@@ -1,38 +0,0 @@
# Copyright 2014-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).
from odoo import models, fields, _
class ResPartner(models.Model):
_inherit = 'res.partner'
def _product_supplied_count(self):
for partner in self:
count = 0
sellers = self.env['product.supplierinfo'].search(
[('name', '=', partner.id)])
if sellers:
count = self.env['product.template'].search_count(
[('seller_ids', 'in', sellers.ids)])
partner.product_supplied_count = count
product_supplied_count = fields.Integer(
compute='_product_supplied_count', string="# of Products Supplied",
)
def show_supplied_products(self):
self.ensure_one()
sellers = self.env['product.supplierinfo'].search(
[('name', '=', self.id)])
ptemplates = self.env['product.template'].search(
[('seller_ids', 'in', sellers.ids)])
action = {
'name': _('Products'),
'type': "ir.actions.act_window",
"res_model": "product.template",
"view_mode": 'tree,kanban,form',
'domain': f"[('id', 'in', {ptemplates.ids})]",
}
return action

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014-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_partner_form" model="ir.ui.view">
<field name="name">add.shortcut.to.product.variants.partner.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_inline oe_stat_button" type="object"
name="show_supplied_products"
icon="fa-shopping-basket"
attrs="{'invisible': [('product_supplied_count', '=', 0)]}">
<field string="Products Supplied" name="product_supplied_count"
widget="statinfo"/>
</button>
</div>
</field>
</record>
</odoo>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,15 +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..3ae56105b44 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
@@ -169,7 +169,9 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
}
}
async _finalizeValidation() {
- if ((this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) && this.env.pos.config.iface_cashdrawer) {
+ //if ((this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) && this.env.pos.config.iface_cashdrawer) {
+ // Always open cashbox (by default, Odoo only opens cashbox for cash payments)
+ if (this.env.pos.config.iface_cashdrawer) {
this.env.pos.proxy.printer.open_cashbox();
}

View File

@@ -1,69 +0,0 @@
diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 828d0bcf0f4..fd4a9e464f5 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -332,14 +332,16 @@ class PosSession(models.Model):
self._create_picking_at_end_of_session()
# Users without any accounting rights won't be able to create the journal entry. If this
# case, switch to sudo for creation and posting.
- try:
- with self.env.cr.savepoint():
- self.with_company(self.company_id)._create_account_move()
- except AccessError as e:
- if sudo:
- self.sudo().with_company(self.company_id)._create_account_move()
- else:
- raise e
+ # AKRETION HACK 20/10/2023 disable savepoint() because I get some
+ # crash upon pos session closing
+ #try:
+ # with self.env.cr.savepoint():
+ # self.with_company(self.company_id)._create_account_move()
+ #except AccessError as e:
+ # if sudo:
+ self.sudo().with_company(self.company_id)._create_account_move()
+ # else:
+ # raise e
if self.move_id.line_ids:
# Set the uninvoiced orders' state to 'done'
self.env['pos.order'].search([('session_id', '=', self.id), ('state', '=', 'paid')]).write({'state': 'done'})
@@ -506,6 +508,7 @@ class PosSession(models.Model):
sale_key = (
# account
line['income_account_id'],
+ line['income_analytic_account_id'],
# sign
-1 if line['amount'] < 0 else 1,
# for taxes
@@ -810,9 +813,14 @@ class PosSession(models.Model):
tax['account_id'] = tax_rep.account_id.id
date_order = order_line.order_id.date_order
taxes = [{'date_order': date_order, **tax} for tax in taxes]
+ # _get_product_analytic_accounts() is a method of the OCA module product_analytic
+ # from https://github.com/OCA/account-analytic
+ income_analytic_account = order_line.product_id.product_tmpl_id.with_company(
+ order_line.company_id)._get_product_analytic_accounts()['income']
return {
'date_order': order_line.order_id.date_order,
'income_account_id': get_income_account(order_line).id,
+ 'income_analytic_account_id': income_analytic_account and income_analytic_account.id or False,
'amount': order_line.price_subtotal,
'taxes': taxes,
'base_tags': tuple(tax_data['base_tags']),
@@ -860,7 +868,7 @@ class PosSession(models.Model):
return self._credit_amounts(partial_vals, amount, amount_converted)
def _get_sale_vals(self, key, amount, amount_converted):
- account_id, sign, tax_keys, base_tag_ids = key
+ account_id, analytic_account_id, sign, tax_keys, base_tag_ids = key
tax_ids = set(tax[0] for tax in tax_keys)
applied_taxes = self.env['account.tax'].browse(tax_ids)
title = 'Sales' if sign == 1 else 'Refund'
@@ -870,6 +878,7 @@ class PosSession(models.Model):
partial_vals = {
'name': name,
'account_id': account_id,
+ 'analytic_account_id': analytic_account_id,
'move_id': self.move_id.id,
'tax_ids': [(6, 0, tax_ids)],
'tax_tag_ids': [(6, 0, base_tag_ids)],

View File

@@ -13,9 +13,7 @@ class ProductTemplate(models.Model):
('consu', 'Consumable'),
('service', 'Service'),
], string='Product Type', default='consu', required=True, tracking=True)
type = fields.Selection(
compute='_compute_type', store=True, string="Type", # native string = "Product Type"
default=False, required=False)
type = fields.Selection(compute='_compute_type', store=True, string="Type")
def _detailed_type_mapping(self):
return {}
@@ -25,16 +23,3 @@ class ProductTemplate(models.Model):
type_mapping = self._detailed_type_mapping()
for record in self:
record.type = type_mapping.get(record.detailed_type, record.detailed_type)
# to ensure compat with test and demo data
# It's not perfect, we still have a problem when installing the "stock"
# module while product_detailed_type_stock is not installed yet: it creates
# products with type = 'product' and with the inherit below, it sets detailed_type = 'product'
# but this value is only possible once product_detailed_type_stock is installed. Odoo says:
# ValueError: Wrong value for product.template.detailed_type: 'product'
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('type') and vals['type'] != vals.get('detailed_type'):
vals['detailed_type'] = vals['type']
return super().create(vals_list)

View File

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

View File

@@ -1,34 +0,0 @@
# 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.html).
from odoo.tests.common import SavepointCase
class TestProductDetailedType(SavepointCase):
def test_product_detailed_type(self):
p1 = self.env['product.product'].create({
'name': 'Test 1',
'detailed_type': 'service',
})
self.assertEqual(p1.type, 'service')
p2 = self.env['product.product'].create({
'name': 'Test 2',
'detailed_type': 'consu',
})
self.assertEqual(p2.type, 'consu')
def test_product_type_compat(self):
p1 = self.env['product.product'].create({
'name': 'Test 1',
'type': 'service',
})
self.assertEqual(p1.type, 'service')
self.assertEqual(p1.detailed_type, 'service')
p2 = self.env['product.product'].create({
'name': 'Test 1',
'type': 'consu',
})
self.assertEqual(p2.type, 'consu')
self.assertEqual(p2.detailed_type, 'consu')

View File

@@ -1,2 +1 @@
from . import sale_order
from . import account_move

View File

@@ -1,33 +0,0 @@
# 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).
from odoo import models
class AccountMove(models.Model):
_inherit = 'account.move'
def _report_get_sale_pickings(self, sale_order=None):
self.ensure_one()
# the sale_order arg is usefull when using
# py3o_lines_layout_groupby_order() to display the delivery orders
# linked to a specific sale_order
assert self.move_type in ('out_invoice', 'out_refund')
sale_orders = sale_order or self.sale_ids
picking_domain = [
('id', 'in', sale_orders.picking_ids.ids),
('state', '=', 'done'),
('date_done', '<', self.create_date),
('company_id', '=', self.company_id.id),
]
previous_inv = self.env['account.move'].search([
('move_type', 'in', ('out_invoice', 'out_refund')),
('create_date', '<', self.create_date),
('id', 'in', sale_orders.invoice_ids.ids),
('company_id', '=', self.company_id.id),
], limit=1, order='id desc')
if previous_inv:
picking_domain.append(('date_done', '>', previous_inv.create_date))
pickings = self.env['stock.picking'].search(picking_domain)
return pickings

View File

@@ -27,7 +27,6 @@
<field name="client_order_ref"/>
</field>
<button name="action_quotation_send" states="sent,sale" position="after">
<button name="action_quotation_send" type="object" string="Send Order Acknowledgement" attrs="{'invisible': ['|', ('state', 'not in', ('sale', 'done')), ('invoice_count','&gt;=',1)]}"/>
<button name="%(sale.action_report_saleorder)d" type="action" string="Print" states="draft,sent,sale,done"/>
</button>
<xpath expr="//field[@name='order_line']/tree/field[@name='product_template_id']" position="after">

View File

@@ -0,0 +1,2 @@
from . import models
from .hooks import post_init_hook

View File

@@ -0,0 +1,14 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Stock Location Simple",
"summary": "Simplified stock.location menu",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"author": "Akretion",
"website": "http://akretion.com",
"depends": ["stock"],
"data": ["views/stock_location_views.xml"],
"post_init_hook": "post_init_hook",
}

View File

@@ -0,0 +1,10 @@
# Copyright 2021 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import SUPERUSER_ID, api
def post_init_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
env["stock.warehouse"].search([])._check_locations_created_by_warehouse()

View File

@@ -0,0 +1,2 @@
from . import stock_location
from . import stock_warehouse

View File

@@ -0,0 +1,10 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
class StockLocation(models.Model):
_inherit = "stock.location"
is_created_by_warehouse = fields.Boolean()

View File

@@ -0,0 +1,34 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
import pprint
class StockWarehouse(models.Model):
_inherit = "stock.warehouse"
def _get_location_fields(self):
location_fields = []
for field, definition in self.fields_get().items():
if definition.get("relation") == "stock.location":
location_fields.append(field)
return location_fields
def _check_locations_created_by_warehouse(self):
location_ids = self.env["stock.location"]
location_fields = self._get_location_fields()
for rec in self:
for field in location_fields:
location_ids |= getattr(rec, field)
location_ids.write({"is_created_by_warehouse": True})
@api.model
def create(self, vals):
res = super().create(vals)
res._check_locations_created_by_warehouse()
return res

View File

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

View File

@@ -0,0 +1,19 @@
# Copyright 2018-2022 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests import TransactionCase
class TestStockLocationSimple(TransactionCase):
def setUp(self):
super().setUp()
self.env["stock.warehouse"].search([])._check_locations_created_by_warehouse()
def test_location_checked_at_warehouse_creation(self):
warehouse = self.env["stock.warehouse"].create({"name": "Test", "code": "TEST"})
self.assertTrue(warehouse.view_location_id.is_created_by_warehouse)
def test_native_location_checked(self):
location_id = self.env.ref("stock.warehouse0").view_location_id
self.assertTrue(location_id.is_created_by_warehouse)

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 Akretion
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="stock_location_simple_form_view" model="ir.ui.view">
<field name="name">stock.location.simple.form</field>
<field name="model">stock.location</field>
<field name="arch" type="xml">
<form string="Stock Location">
<sheet>
<div class="oe_button_box" name="button_box">
<button string="Current Stock"
class="oe_stat_button"
icon="fa-cubes" name="%(stock.location_open_quants)d" type="action"/>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<label for="location_id" class="oe_edit_only"/>
<h2>
<field name="location_id" required="1" domain="[('usage', '=', 'internal')]"/>
</h2>
<group>
<field name="active" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<field name="comment" placeholder="External note..."/>
</sheet>
</form>
</field>
</record>
<record id="stock_location_simple_tree_view" model="ir.ui.view">
<field name="name">stock.location.simple.tree</field>
<field name="model">stock.location</field>
<field name="arch" type="xml">
<!-- TODO: decoration info if lockdown, if view, if linked to a warehouse -->
<tree string="Stock Location">
<field name="active" invisible="1"/>
<field name="complete_name" string="Location"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="stock_location_simple_act_window" model="ir.actions.act_window">
<field name="name">Stock Location</field>
<field name="res_model">stock.location</field>
<field name="view_mode">tree,form</field>
<field name="domain">[("usage", "=", "internal")]</field>
</record>
<record id="stock_location_simple_act_window_tree" model="ir.actions.act_window.view" >
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="stock_location_simple_tree_view"/>
<field name="act_window_id" ref="stock_location_simple_act_window"/>
</record>
<record id="stock_location_simple_act_window_form" model="ir.actions.act_window.view" >
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="stock_location_simple_form_view"/>
<field name="act_window_id" ref="stock_location_simple_act_window"/>
</record>
<record id="stock_location_simple_menu" model="ir.ui.menu">
<field name="name">Locations</field>
<field name="parent_id" ref="stock.menu_warehouse_config"/>
<field name="action" ref="stock_location_simple_act_window"/>
<field name="sequence" eval="0"/>
<field name="groups_id" eval="[(4, ref('stock.group_stock_multi_locations'))]" />
</record>
<!-- Modify name and groups_id on original stock.location menu -->
<record id="stock.menu_action_location_form" model="ir.ui.menu">
<field name="name">Locations Technical</field>
<field name="parent_id" ref="stock.menu_warehouse_config"/>
<field name="action" ref="stock.action_location_form"/>
<field name="sequence" eval="2"/>
<field name="groups_id" eval="[(4, ref('base.group_erp_manager'))]" />
</record>
</odoo>

View File

@@ -5,7 +5,6 @@ from . import stock_location
from . import stock_location_route
from . import stock_warehouse_orderpoint
from . import stock_quant
from . import stock_quant_package
from . import stock_inventory
from . import procurement_group
from . import procurement_scheduler_log

View File

@@ -2,11 +2,7 @@
# @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, float_is_zero
from odoo.exceptions import UserError
import logging
logger = logging.getLogger(__name__)
from odoo import fields, models
class StockQuant(models.Model):
@@ -20,7 +16,7 @@ class StockQuant(models.Model):
action = self.env["ir.actions.actions"]._for_xml_id(
"stock.stock_move_line_action")
action['domain'] = [
('state', 'not in', ('draft', 'done', 'cancel')),
('state', 'not in', ('draft', 'done')),
('product_id', '=', self.product_id.id),
('location_id', '=', self.location_id.id),
('lot_id', '=', self.lot_id.id or False),
@@ -30,48 +26,3 @@ class StockQuant(models.Model):
]
action['context'] = {'create': 0, 'stock_move_line_main_view': True}
return action
@api.model
def _fix_reserved_quantity(self):
# called by a script when we start to have wrong 'reserved_quantity' on quants
logger.info('START _fix_reserved_quantity')
self._cr.execute("DELETE FROM stock_move_line WHERE move_id is null")
self._cr.execute('SELECT id FROM stock_quant WHERE reserved_quantity > quantity AND reserved_quantity > 0')
qty_quant_digits = 4
all_rows = self._cr.fetchall()
logger.info('%d bad quants detected', len(all_rows))
for row in all_rows:
quant_id = row[0]
quant = self.env['stock.quant'].browse(quant_id)
logger.info('Processing quant ID %s product %s lot %s location %s package %s quantity %s reserved_quantity %s', quant.id, quant.product_id.display_name, quant.lot_id.display_name or '-', quant.location_id.display_name, quant.package_id.display_name, quant.quantity, quant.reserved_quantity)
reserved_quantity_cmp = float_compare(quant.reserved_quantity, 0, precision_digits=qty_quant_digits)
# should never happen
if reserved_quantity_cmp < 0:
raise UserError(_("On quant ID %d, reserved_quantity is negative (%s).") % (quant_id, quant.reserved_quantity))
if not reserved_quantity_cmp:
logger.info('Quant ID %d as reserved_quantity = 0 after proper rounding', quant.id)
continue
if quant.package_id:
logger.warning("Skipping quant ID %d because this script doesn't support packages for the moment", quant.id)
continue
if quant.location_id.should_bypass_reservation():
logger.warning('Skipping quant ID %d because the location is marked as should bypass resa', quant.id)
continue
ml_domain = [
('product_id', '=', quant.product_id.id),
('location_id', '=', quant.location_id.id),
('state', 'not in', ('done', 'draft', 'cancel')),
('lot_id', '=', quant.lot_id.id or False),
('company_id', '=', quant.company_id.id),
]
move_lines = self.env['stock.move.line'].sudo().search(ml_domain)
if not move_lines:
quant.sudo().write({'reserved_quantity': 0})
for move_line in move_lines:
move_line.button_do_unreserve()
move_lines = self.env['stock.move.line'].sudo().search(ml_domain)
if not move_lines:
quant.sudo().write({'reserved_quantity': 0})
else:
raise UserError(_("Product moves are still present after full unreserve on quant ID %d. This should never happen.") % quant.id)
logger.info('END _fix_reserved_quantity')

View File

@@ -1,31 +0,0 @@
# Copyright 2024 Akretion France (https://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models
import logging
logger = logging.getLogger(__name__)
class StockQuantPackage(models.Model):
_inherit = "stock.quant.package"
@api.model
def _cron_auto_unpack_on_internal_locations(self):
# Problem in v10: when you manage packs in Odoo for customer pickings,
# you have the following problem: when you return a customer picking,
# if you return all the products that were in the same pack, the pack
# is returned, so you have in your stock one or several quants
# inside a pack. This is a problem when you want to ship those
# products again.
# I provide the code in this module, but not the cron, because in some
# scenarios, you may want to have packs in your stock.
# Just add the cron in the specific module of your project.
# Underlying problem solved in Odoo v11. Don't port that to v14 !
logger.info('START cron auto unpack on internal locations')
int_locs = self.env['stock.location'].search([('usage', '=', 'internal')])
packages = self.search([('location_id', 'in', int_locs.ids)])
logger.info('Unpacking %d packages on internal locations', len(packages))
packages.unpack()
logger.info('END cron auto unpack on internal locations')

View File

@@ -2,7 +2,7 @@
# @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 import api, models
import logging
logger = logging.getLogger(__name__)
@@ -11,8 +11,6 @@ logger = logging.getLogger(__name__)
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
product_barcode = fields.Char(related='product_id.barcode', string="Product Barcode")
@api.model
def _procure_orderpoint_confirm(
self, use_new_cursor=False, company_id=False, raise_user_error=True):

View File

@@ -22,9 +22,6 @@
<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="product_barcode" optional="hide"/>
</field>
<field name="trigger" position="attributes">
<attribute name="optional">show</attribute>
</field>