Compare commits

...

35 Commits

Author SHA1 Message Date
Sébastien BEAU
7b8709a425 Add product_editable_sequence 2025-01-23 12:14:40 +01:00
Alexis de Lattre
1d96cc4c83 stock_usability: add tracking on important fields of stock.production.lot
Code cleanup
2024-12-19 17:30:14 +01:00
beau sebastien
2ff3ea6dcd Merge pull request #214 from akretion/14.0-fix-product_usability
product_usability: seller_id can not be store as muti-company will be broken
2024-12-12 01:39:21 +01:00
Sébastien BEAU
a1f8ab32dd product_usability: make the field seller_id searcheable 2024-12-12 01:38:25 +01:00
Alexis de Lattre
f3a6cfade6 [IMP] account_bank_reconciliation_summary_xlsx: add block to have difference et justification (entered by user) 2024-10-23 12:20:26 +02:00
Alexis de Lattre
b8203ae42a [IMP] account_bank_reconciliation_summary_xlsx fine-tune config check 2024-10-22 15:11:01 +02:00
Alexis de Lattre
02d9dee32e [IMP] account_bank_reconciliation_summary_xlsx: use native method to compute account balance 2024-10-22 15:05:32 +02:00
Alexis de Lattre
04b363f8a6 [MIG] mass_mailing_usability to v14 2024-10-22 14:39:45 +02:00
Alexis de Lattre
f3b5f85fc9 [IMP] account_bank_reconciliation_summary_xlsx: support bank journals in currency different than company currency
Update fr translation
2024-10-22 14:34:45 +02:00
Alexis de Lattre
d7f6a2b9f8 [IMP] account_payment_line_manual_account: add support for analytic 2024-10-16 17:13:23 +02:00
Florian
2a0b1342e7 Merge pull request #222 from akretion/14-fix-invoice-update-pdf-unlink
[FIX] account_invoice_update_wizard : avoid stacktrace when no pdf is…
2024-10-10 09:18:57 +02:00
Florian da Costa
db33820a57 [FIX] account_invoice_update_wizard : avoid stacktrace when no pdf is found 2024-10-10 09:12:48 +02:00
Alexis de Lattre
4c067fef09 stock_usability: mig method auto unpack on internal locations from v10 2024-10-01 11:02:26 +02:00
Alexis de Lattre
88452b7930 [IMP] account_usability_akretion: simplify code for invoice attach del upon invoice back to draft 2024-09-30 10:56:47 +02:00
Alexis de Lattre
a6305eb581 [FIX] account_usability: fix deletion of PDF invoice attachment when invoice is back to draft 2024-09-30 09:03:30 +02:00
beau sebastien
d2a8b77c22 Merge pull request #221 from akretion/14.0-purge-pdf
account_invoice_update_wizard: purge existing pdf in attachment after update
2024-09-27 17:25:24 +02:00
Sébastien BEAU
467ce47306 account_invoice_update_wizard: purge existing pdf in attachment after update 2024-09-27 17:11:39 +02:00
Alexis de Lattre
ff0bdc1b8d sale_stock_usability: add method to display delivery order on out invoice report 2024-09-23 21:45:30 +00:00
Alexis de Lattre
254a97edd3 base_usability: Add rcs_siren in company header 2024-09-23 20:56:02 +00:00
Alexis de Lattre
162f0b7874 mrp_usability: add tracking on some fields 2024-09-19 16:04:03 +02:00
Alexis de Lattre
ee8bf2ea17 stock_usability: add script to fix reserved_quantity on quants
Filter-out cancelled move lines shown by "reserved" button on quants
2024-09-19 16:03:08 +02:00
Alexis de Lattre
97c20fed73 mrp_subcontracting_usability: add link to subcontrating production order from stock.move form view 2024-09-19 11:22:59 +02:00
Alexis de Lattre
856bca4ccf account_usability: add search on account in account.move search view 2024-09-17 17:53:28 +02:00
Alexis de Lattre
0d5346d856 Add module account_payment_line_manual_account 2024-09-14 00:13:14 +02:00
Alexis de Lattre
940fe43614 [MIG] partner_products_shortcut from v10 to v14 2024-09-12 22:09:25 +02:00
Alexis de Lattre
4e68c48110 stock_usability: Add product_barcode on orderpoint tree view (optional hide) 2024-09-12 21:28:51 +02:00
Alexis de Lattre
7b8c35a384 Add patch in pos_usability 2024-09-10 23:37:46 +02:00
Alexis de Lattre
0bbda6f265 product_detailed_type: improve compatibility with demo/test data
It's still not perfect yet, in particular when you install the "stock"
module and "product_detailed_type_stock" is not installed yet.
2024-09-05 18:21:59 +02:00
Alexis de Lattre
c7c5d9172b pos_usability: add patch pos-always_open_cashbox.diff 2024-07-15 21:57:09 +02:00
Alexis de Lattre
4655e6b739 base_usability: add 2 patches 2024-07-11 13:04:59 +02:00
Alexis de Lattre
8bd83b0975 mrp_average_cost: avoid crash on bad data 2024-06-25 01:10:51 +02:00
Alexis de Lattre
9367f7006e [FIX] mrp_average_cost: fix bad dependency 2024-06-24 18:31:35 +02:00
Alexis de Lattre
29b8ebb779 account_usability: add invoice currency signed fields
Show these fields as optional in tree view without sum=1
2024-06-24 11:24:22 +02:00
Alexis de Lattre
c33835957d sale_usability: add button to send order acknowledgement
This is a forward port of a v12 commit.
2024-06-24 10:03:13 +02:00
Sébastien BEAU
0fa2024dab product_usability: seller_id can not be store as muti-company will be broken
do not store the field as shared product can have different supplier
depending on the company logged
Use compute as related will always pick the first one whatever the
company logged
2024-06-02 22:25:10 +02:00
59 changed files with 985 additions and 177 deletions

View File

@@ -1,10 +1,10 @@
# Copyright 2017-2023 Akretion France (http://www.akretion.com/)
# Copyright 2017-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": "Bank Reconciliation Report",
"version": "14.0.1.0.0",
"version": "14.0.2.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: 2023-01-13 10:31+0000\n"
"PO-Revision-Date: 2023-01-13 10:31+0000\n"
"POT-Creation-Date: 2024-10-23 10:04+0000\n"
"PO-Revision-Date: 2024-10-23 10:04+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -17,6 +17,7 @@ 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"
@@ -27,6 +28,12 @@ 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"
@@ -49,7 +56,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 rapport de rapprochement bancaire"
msgstr "Assistant de rapport de rapprochement bancaire"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.actions.report,name:account_bank_reconciliation_summary_xlsx.bank_reconciliation_xlsx
@@ -96,17 +103,36 @@ 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 ""
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 :"
#. 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 ""
msgstr "Nom affiché"
#. 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
@@ -123,13 +149,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 ""
msgstr "Exporter en XLSX"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Generated on %s"
msgstr "Généré le %s"
msgid "Generated from Odoo on %s by %s"
msgstr "Généré par Odoo le %s par %s"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__id
@@ -143,6 +169,18 @@ 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
@@ -153,23 +191,17 @@ 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 ""
msgstr "Dernière modification le"
#. 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 ""
msgstr "Dernière mise à jour par"
#. 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 ""
#. 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"
msgstr "Dernière mise à jour le"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
@@ -179,10 +211,23 @@ 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
@@ -211,3 +256,13 @@ 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-2023 Akretion France (http://www.akretion.com/)
# Copyright 2017-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).
@@ -14,32 +14,19 @@ class BankReconciliationXlsx(models.AbstractModel):
_description = "Bank Reconciliation XLSX Report"
_inherit = "report.report_xlsx.abstract"
def _domain_add_move_state(self, wizard, domain):
if wizard.move_state == 'posted':
domain.append(('parent_state', '=', 'posted'))
elif wizard.move_state == 'draft_posted':
domain.append(('parent_state', 'in', ('draft', 'posted')))
def _get_account_balance(self, account, wizard):
def _prepare_payment_move_lines(self, jdi, account, unreconciled_only=True):
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),
("company_id", "=", jdi['company'].id),
("account_id", "=", account.id),
("journal_id", "=", journal.id),
("date", "<=", wizard.date),
("journal_id", "=", jdi['journal'].id),
("date", "<=", jdi['wizard'].date),
]
if jdi['wizard'].move_state == 'posted':
domain.append(('parent_state', '=', 'posted'))
elif jdi['wizard'].move_state == 'draft_posted':
domain.append(('parent_state', 'in', ('draft', 'posted')))
if unreconciled_only:
limit_datetime_naive = datetime.combine(wizard.date, datetime.max.time())
limit_datetime_naive = datetime.combine(jdi['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)
@@ -47,7 +34,6 @@ 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:
@@ -60,13 +46,18 @@ 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": mline.balance,
"amount": amount,
"move_name": move.name,
"counterpart": counterpart,
}
@@ -74,27 +65,31 @@ 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['journal'], account, jdi['wizard'])
mlines = self._prepare_payment_move_lines(jdi, account)
if mlines or add2total:
sheet.write(row, 0, '%s %s' % (account.name, account.code), style['title' + style_suffix])
sheet.write(row, 1, "", style['title' + style_suffix])
sheet.write(row, 0, '%s %s' % (account.name, account.code), style[f"title{style_suffix}"])
sheet.write(row, 1, "", style[f"title{style_suffix}"])
if not mlines:
if add2total:
sheet.write(row, 2, _("None"), style['none'])
else:
return
return row
else:
row += 1
col_labels = [
_("Date"),
_("Partner"),
_("Amount"),
_("Move Number"),
_("Journal Entry"),
_("Counter-part"),
_("Ref."),
_("Label"),
@@ -108,7 +103,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['currency'])
sheet.write(row, 2, mline["amount"], style[jdi['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'])
@@ -118,18 +113,20 @@ class BankReconciliationXlsx(models.AbstractModel):
end_line = row
for col in range(1):
sheet.write(row, col, "", style['title' + style_suffix])
sheet.write(row, 1, _("Sub-total:") + ' ', style['title_right' + style_suffix])
sheet.write(row, col, "", style[f"title{style_suffix}"])
sheet.write(row, 1, _("Sub-total:") + ' ', style[f"title_right{style_suffix}"])
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)
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)
if add2total:
jdi['total'] += subtotal
jdi['total_formula'] += '+%s%d' % (jdi['total_col'], row + 1)
return row
jdi['total_formula'] += f"+{jdi['total_col']}{row + 1}"
return row + 2
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
@@ -137,15 +134,21 @@ 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 on %s') % format_datetime(
self.env, datetime.utcnow())
generated_on_label = _('Generated from Odoo on %s by %s') % (
format_datetime(self.env, datetime.utcnow()),
self.env.user.name)
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': '=',
@@ -169,7 +172,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, wizard.company_id.display_name, style['wizard_value'])
sheet.write(row, 1, company.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'])
@@ -177,51 +180,121 @@ 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'])
account_bal = self._get_account_balance(bank_account, wizard)
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)
sheet.write(row, 2, account_bal, style['currency_bg'])
sheet.write(row, 2, account_bal, style[f"{jdi['currency']}_bg"])
jdi['total'] += account_bal
jdi['total_formula'] += '%s%d' % (jdi['total_col'], row + 1)
jdi['total_formula'] += f"{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['currency_bg'], jdi['total'])
row += 3
row, 2, jdi['total_formula'], style[f"{jdi['currency']}_bg"], jdi['total'])
total_row = row
row += 2
# 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"
title_blue = "#e6e6fa"
light_blue = "#e0edff"
subtotal_orange = "#ffcc00"
title_warn = "#ff9999"
subtotal_warn = "#ffff99"
light_purple = "#ffdeff"
amount_manual = "#ffeeab"
title_warn = "#ff9999"
lang_code = self.env.user.lang
lang = False
if lang_code:
@@ -249,7 +322,7 @@ class BankReconciliationXlsx(models.AbstractModel):
)
title_style = {
"bold": True,
"bg_color": title_blue,
"bg_color": light_blue,
"font_size": font_size,
"align": "left",
}
@@ -257,7 +330,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_purple,
"bg_color": light_blue,
"bold": True,
"font_size": font_size,
"align": "left",
@@ -274,23 +347,29 @@ 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})
style['regular'] = workbook.add_format({"font_size": font_size, "border": 1})
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"}
{"num_format": xls_date_format, "font_size": font_size, "align": "left", "border": 1}
)
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))
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))
return style

View File

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

View File

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

View File

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

View File

@@ -227,6 +227,12 @@ 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

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

View File

@@ -0,0 +1,25 @@
# 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

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

View File

@@ -0,0 +1,38 @@
# 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

@@ -0,0 +1,18 @@
# 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

@@ -0,0 +1,35 @@
<?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

@@ -52,6 +52,39 @@ 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):
@@ -225,45 +258,14 @@ class AccountMove(models.Model):
move.suitable_journal_ids = self.env['account.journal'].search(domain)
def button_draft(self):
# Get report name before reset to draft because name can be different.
report_names = self._get_invoice_attachment_name()
super().button_draft()
# Delete attached pdf invoice
if report_names:
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', 'in', report_names[move.id]),
('res_id', '=', move.id),
('res_model', '=', self._name),
('type', '=', 'binary'),
], limit=1)
if attachment:
attachment.unlink()
def _get_invoice_attachment_name(self):
report_names = defaultdict(list)
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')):
report_names[move.id].append(safe_eval(report_invoice.print_report_name, {'object': self, 'time': time}))
try:
report_invoice = self.env['ir.actions.report']._get_report_from_name('account.report_invoice_with_payments')
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')):
report_names[move.id].append(safe_eval(report_invoice.print_report_name, {'object': self, 'time': time}))
return report_names
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()
def _get_accounting_date(self, invoice_date, has_tax):
# On vendor bills/refunds, we want date = invoice_date unless

View File

@@ -67,6 +67,14 @@
<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>
@@ -93,6 +101,16 @@
</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

@@ -0,0 +1,13 @@
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,6 +69,10 @@ 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:'),
@@ -102,7 +106,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', 'capital'], ['vat', 'siret', 'eori', 'ape']]
line_details = [['phone', 'website', 'rcs_siren', 'capital'], ['vat', 'siret', 'eori', 'ape']]
return line_details
# for reports

View File

@@ -0,0 +1,16 @@
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

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

View File

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

View File

@@ -0,0 +1,14 @@
# 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,39 +8,13 @@
<odoo>
<record id="view_link_tracker_click_tree" model="ir.ui.view">
<record id="link_tracker_click_view_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="link_tracker.view_link_tracker_click_tree"/>
<field name="inherit_id" ref="mass_mailing.link_tracker_click_view_tree"/>
<field name="arch" type="xml">
<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 name="mass_mailing_id" position="after">
<field name="mailing_trace_id" optional="hide"/>
</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'],
'depends': ['mrp_account'],
'data': [
'security/mrp_average_cost_security.xml',
'security/ir.model.access.csv',

View File

@@ -165,7 +165,9 @@ class MrpProduction(models.Model):
company_currency_id = fields.Many2one(
related='company_id.currency_id', string='Company Currency')
# extra_cost is per unit in the UoM of the mrp.production (product_uom_id)
# 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 = fields.Float(
compute='_compute_extra_cost', store=True, readonly=False,
help="For a regular production order, it takes into account the labor cost "
@@ -178,7 +180,7 @@ class MrpProduction(models.Model):
def _compute_extra_cost(self):
for prod in self:
bom = prod.bom_id
if bom and bom.type == 'normal':
if bom and bom.type == 'normal' and bom.product_uom_id.category_id == prod.product_uom_id.category_id:
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

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
# 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 2023 Akretion France (http://www.akretion.com/)
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).
-->

View File

@@ -0,0 +1,23 @@
<?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

@@ -0,0 +1,23 @@
<?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,6 +19,10 @@ 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

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

View File

@@ -0,0 +1,27 @@
# 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

@@ -0,0 +1,38 @@
# 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

@@ -0,0 +1,27 @@
<?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.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,15 @@
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

@@ -0,0 +1,69 @@
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,7 +13,9 @@ 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")
type = fields.Selection(
compute='_compute_type', store=True, string="Type", # native string = "Product Type"
default=False, required=False)
def _detailed_type_mapping(self):
return {}
@@ -23,3 +25,16 @@ 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

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

View File

@@ -0,0 +1,34 @@
# 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

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

View File

@@ -0,0 +1,26 @@
# Copyright 2024 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).
{
"name": "Product Editable Sequence",
"summary": "Make s=the sequence mass editable with a default value of 100",
"version": "14.0.1.0.0",
"development_status": "Alpha",
"category": "Uncategorized",
"website": "www.akretion.com",
"author": " Akretion",
"license": "AGPL-3",
"external_dependencies": {
"python": [],
"bin": [],
},
"depends": [
"product",
],
"data": [
"views/product_template_views.xml",
],
"demo": [
],
}

View File

@@ -0,0 +1,11 @@
# Copyright 2024 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 _, api, fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
sequence = fields.Integer(default=100)

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="product_template_view_tree" 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="sequence" position="attributes">
<attribute name="widget"/>
<attribute name="readonly"/>
</field>
</field>
</record>
</odoo>

View File

@@ -14,7 +14,9 @@ class ProductTemplate(models.Model):
# in v10, that field was defined in procurement_suggest, but we will
# probably not port procurement_suggest because it is native in v14
seller_id = fields.Many2one(
'res.partner', related='seller_ids.name', store=True,
'res.partner',
compute="_compute_seller_id",
search="_search_seller_id",
string='Main Supplier')
# in v14, I noticed that the tracking of the fields of product.template
@@ -33,6 +35,19 @@ class ProductTemplate(models.Model):
company_id = fields.Many2one(tracking=110)
barcode_type = fields.Char(compute='_compute_template_barcode_type')
def _search_seller_id(self, operator, value):
# searching on the first line of a o2m is not that easy
# So we search all potential matching products
# Then we filter on the seller_id
records = self.search([("seller_ids.partner_id", operator, value)])
records = records.filtered_domain([("seller_id", operator, value)])
return [("id", "in", records.ids)]
def _compute_seller_id(self):
for record in self:
record.seller_id = fields.first(record.seller_ids).name
@api.depends('product_variant_ids.barcode')
def _compute_template_barcode_type(self):
ppo = self.env['product.product']

View File

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

View File

@@ -0,0 +1,33 @@
# 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,6 +27,7 @@
<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

@@ -5,7 +5,9 @@ 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 stock_production_lot
from . import procurement_group
from . import procurement_scheduler_log
from . import product

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 fields, models
from odoo import models
class StockLocation(models.Model):

View File

@@ -0,0 +1,13 @@
# 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 StockProductionLot(models.Model):
_inherit = 'stock.production.lot'
name = fields.Char(tracking=True)
product_id = fields.Many2one(tracking=True)
ref = fields.Char(tracking=True)

View File

@@ -2,7 +2,11 @@
# @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 import api, fields, models, _
from odoo.tools import float_compare
from odoo.exceptions import UserError
import logging
logger = logging.getLogger(__name__)
class StockQuant(models.Model):
@@ -16,7 +20,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')),
('state', 'not in', ('draft', 'done', 'cancel')),
('product_id', '=', self.product_id.id),
('location_id', '=', self.location_id.id),
('lot_id', '=', self.lot_id.id or False),
@@ -26,3 +30,48 @@ 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

@@ -0,0 +1,31 @@
# 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, models
from odoo import api, fields, models
import logging
logger = logging.getLogger(__name__)
@@ -11,6 +11,8 @@ 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,6 +22,9 @@
<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>