Compare commits

...

179 Commits

Author SHA1 Message Date
Alexis de Lattre
533023c32d [IMP] base_partner_one2many_phone: improve mig script 2025-05-20 14:25:02 +02:00
Alexis de Lattre
eb2386d0cd product_category_tax: code cleanup 2023-02-19 23:46:33 +01:00
Alexis de Lattre
4a9bf263e3 product_category_tax: set taxes in all companies 2023-02-19 23:21:32 +01:00
Alexis de Lattre
0358127f0c product_category_tax: add field in constraint 2023-02-13 22:03:05 +01:00
Alexis de Lattre
00df54ee37 stock_valuation_xlsx: improve multi-company support
Other small minor improvements/fixes
2022-05-13 17:35:50 +02:00
Alexis de Lattre
fdf204b3aa mail_usability: Add display_address_mail_template on res.partner 2022-02-17 15:56:47 +01:00
Alexis de Lattre
ce2b927c35 fix return wizard "set qty to 0"
Add method to auto unpack on return
2022-02-17 15:56:11 +01:00
Alexis de Lattre
4e57866115 purchase_usability: fix perf issue: we can't show a non-computed field in tree view 2022-01-27 23:40:45 +01:00
Alexis de Lattre
2922b19e9e sale_order_add_bom: add kit wizard now available on pickings 2021-11-25 18:50:47 +01:00
Alexis de Lattre
b8132c7f2e stock_valuation_xlsx: add depreciation ratios 2021-06-21 19:09:20 +02:00
Alexis de Lattre
4e6d93c625 mrp_average_cost: Minor code improvements 2021-06-21 18:40:38 +02:00
Alexis de Lattre
45bc579e97 Add carrier_id in sale.report 2021-05-25 10:42:41 +02:00
Alexis de Lattre
24cc7e1eaa sale_stock_usability: update depends key in relation to my previous commit 2021-05-21 11:12:02 +02:00
Alexis de Lattre
1c11a768b4 sale_stock_usability: Add field to_refund_so in stock.move form 2021-05-21 10:39:47 +02:00
Alexis de Lattre
781e8989f5 Add filter no_attachment on sale.order (same as on account.invoice) 2021-05-20 12:25:33 +02:00
Alexis de Lattre
d858634b91 Add mass cancel procurement wizard 2021-04-26 11:50:11 +02:00
Alexis de Lattre
ff69a23e17 account_usability: improve name of account.move.line generated from invoices for customer/supplier account
Don't always prefix name of account.move.line generated from invoices
with invoice number, only for that when name is '/'
2021-03-08 15:30:00 +01:00
Alexis de Lattre
4f4d3323d4 stock_valuation_xlsx: fix crash when start valuation = current for variation wizard 2021-02-26 10:57:23 +01:00
Alexis de Lattre
ec7601c59d purchase_usability: set company_id as invisible in tree view of purchase order lines (to save space) 2021-01-28 22:17:52 +01:00
Alexis de Lattre
bbb556c30c Small improvements in stock_valuation_xlsx 2021-01-16 00:11:57 +01:00
Alexis de Lattre
cf93f340b4 stock_valuation_xlsx: add stock.variation.xlsx report 2021-01-14 21:52:02 +01:00
Alexis de Lattre
a17cdbcc60 Revive stock_account_usability in v10 ! 2021-01-12 16:30:03 +01:00
Alexis de Lattre
16d382ec42 Don't mark invoice as 'sent' when you just click on 'Print Invoice' 2020-11-03 18:03:02 +01:00
Alexis de Lattre
efed3e3880 [FIX] stock_valuation_xlsx: bad context key for past stock level 2020-11-03 17:18:26 +01:00
Alexis de Lattre
11882c712c account_usability: Use partner_id.display_name (instead of partner_id.name) in reconcile work interface
Update account_several_improvements.diff (only line number change, no code change)
2020-11-03 16:12:27 +01:00
Alexis de Lattre
278cf0713c sale_usability: inherit product_uom_change() to add a warning when price is updated upon qty change 2020-11-03 15:14:38 +01:00
Alexis de Lattre
18a2998bc3 sale_confirm_wizard: add ability to skip wizard via inherit in some scenarios 2020-11-03 14:54:47 +01:00
Alexis de Lattre
9172407699 sale_confirm_wizard: don't show main block when sale_warn = block 2020-11-02 17:59:10 +01:00
Alexis de Lattre
e1d026d2b8 sale_confirm_warning: add sale_warn 2020-11-02 17:52:08 +01:00
Alexis de Lattre
5c38be2122 Add HT/TTC field on product.template and supplierinfo
Add french translation (partial)
2020-10-24 00:01:16 +02:00
Alexis de Lattre
9152a2c1a4 mail_usability: show a nice (native) option on mail compose wizard 2020-10-24 00:00:32 +02:00
Alexis de Lattre
844c9cc08c base_partner_one2many_phone: add compute_sudo=True 2020-10-22 12:03:14 +02:00
Alexis de Lattre
58ddff0402 Add a filter "This year and previous" on account.invoice.report 2020-10-12 12:30:53 +02:00
Alexis de Lattre
a748de39ec [FIX] stock_valuation_xlsx: fix report when categ_subtotal is false 2020-09-26 00:01:27 +02:00
Alexis de Lattre
446c2c1f1d stock_valuation_xlsx: Improve perfs
Add ability to force cost price to current
Improve headers in XLSX
2020-09-25 22:50:20 +02:00
Alexis de Lattre
063924fdc7 stock_valuation_xlsx: Speed-up for past valuation when few of the total products are in stock
Closer to PEP8
2020-09-25 12:19:26 +02:00
Alexis de Lattre
1e4de02259 stock_valuation_xlsx: Improve module description 2020-09-22 23:57:38 +02:00
Alexis de Lattre
4b432ec207 stock_valuation_xlsx: add possibility to add custom products fields in report 2020-09-22 15:25:54 +02:00
Alexis de Lattre
1b4f9dfab1 stock_valuation_xlsx: Add button on inventory form
Display expiry date as date
Add debug messages
Don't hardcode currency in style
Use decimal precision for price too
2020-09-22 10:42:45 +02:00
Alexis de Lattre
3043ad11a8 stock_inventory_xlsx: small fixes 2020-09-22 00:15:07 +02:00
Alexis de Lattre
de5470e5bd Add module stock_valuation_xlsx
stock_inventory_valuation_ods: fix module description
2020-09-21 23:36:02 +02:00
Chafique
31483abb99 add mail_follower_option module 2020-07-17 11:38:22 +02:00
Alexis de Lattre
6377f0984d stock_usability: Add 'set qty to 0' button on return wizard 2020-07-06 17:26:31 +02:00
Alexis de Lattre
284f0a1e73 partner_products_shortcut: don't use truck logo for something that doesn't point to a picking, use list logo instead 2020-07-06 11:03:16 +02:00
Alexis de Lattre
da81278b17 Add archive filter 2020-05-26 22:38:38 +02:00
Alexis de Lattre
f792979456 Add index=True on domain 2020-05-26 17:11:07 +02:00
Alexis de Lattre
d9a598a3c4 Add new module base_dynamic_list 2020-05-26 17:09:53 +02:00
Alexis de Lattre
2b74514230 mass_mailing_usability: Fix dependencies 2020-04-24 09:27:40 +02:00
Alexis de Lattre
38f1eacf8f sale_stock_usability: add methods for report
move form view in picking: add date field
account_usability: amount in tax lines readonly on customer invoices
2020-03-06 18:33:49 +01:00
Alexis de Lattre
aa9ab68ca3 Add incoterm in sale config menu, because incoterms is managed by sales manager in 99% of companies 2020-03-05 19:44:50 +01:00
Alexis de Lattre
97a83b0615 stock_usability: Add compute_sudo=True on some related fields of stock.inventory.line to avoid ACL issues 2020-01-29 22:37:36 +01:00
Alexis de Lattre
f6642639cf one2many_phone: Workaround a bug "Record does not exist or has been deleted." 2020-01-27 23:32:38 +01:00
Alexis de Lattre
cc11aca053 one2many_phone: Update search view 2020-01-27 21:50:04 +01:00
Alexis de Lattre
104ae274e9 Update translation for base_partner_one2many_phone 2020-01-27 19:03:52 +01:00
Alexis de Lattre
2baf9167a4 Add mobile in partner tree view 2020-01-13 09:57:10 +01:00
Alexis de Lattre
af6550fd2b base_partner_one2many_phone: improve mig script, add form view for res.partner.phone 2020-01-10 16:35:03 +01:00
Alexis de Lattre
bb23254830 Big update of base_partner_one2many_phone: new types, add email support
Migration script provided
2020-01-10 16:02:03 +01:00
Alexis de Lattre
cc0da43bdc commission_simple: improve view 2019-12-10 00:16:21 +01:00
Alexis de Lattre
57236ba173 Small fixes in commission modules 2019-12-10 00:08:39 +01:00
Alexis de Lattre
80480c99cc commission_simple: fix error in sql constraint 2019-12-09 23:01:06 +01:00
Alexis de Lattre
edb93dda3d Add module commission_simple and commission_simple_sale
Improve view inheritance in account_usability
2019-12-09 22:52:33 +01:00
Alexis de Lattre
9020ab18f6 Remove _rec_name from mrp.bom because there is now a native name_get() 2019-12-02 11:37:41 +01:00
Alexis de Lattre
01cfcbf80d sale_order_add_bom: fix related field definition 2019-12-02 11:21:59 +01:00
Alexis de Lattre
69f283f387 Add group on wizard entry 2019-11-28 17:10:27 +01:00
Alexis de Lattre
e884489c9b Add mass backtodraft wizard on account.move 2019-11-28 17:08:09 +01:00
Alexis de Lattre
4eb7969264 base_partner_ref: proper invalidation for display_name 2019-10-29 15:39:47 +01:00
Alexis de Lattre
a8019b2c80 Add option "hide_bank_statement_balance" on account.journal 2019-09-06 23:45:31 +02:00
Alexis de Lattre
f616e23985 Add code in journal tree view
Remove inherit of name_search, because it was already native in a
slightly different implementation
2019-08-07 11:31:49 +02:00
Alexis de Lattre
651cd27118 Merge branch '10.0' of github.com:akretion/odoo-usability into 10.0 2019-06-20 23:27:47 +02:00
Alexis de Lattre
acdddf0d08 Improve views for sale.report and purchase.report, and search view for sale order 2019-06-20 23:27:12 +02:00
David Beal
c6cb2e197f Merge pull request #95 from MindAndGo/10.0-bad-menu-reference
[10.0]FIX : Bad menu used
2019-06-17 16:51:30 +02:00
Florent THOMAS
363f781acf Cahnge the menu used 2019-06-12 16:48:24 +02:00
Pierrick Brun
1364748052 Merge pull request #88 from akretion/10-account_report_qweb_horizontal
[IMP] 10.0 account report qweb horizontal
2019-06-11 10:21:21 +02:00
Alexis de Lattre
73ee7d4dec Add module mass_mailing_usability
Improve module link_tracker_usability
2019-05-24 19:12:29 +02:00
Alexis de Lattre
5f207046fb New module link_tracker_usability 2019-05-17 20:27:52 +02:00
Alexis de Lattre
b4dfab1bf9 "Show Inventory Lines" renamed to "Inventory Lines" 2019-05-13 23:21:25 +02:00
Alexis de Lattre
e56343c181 account_move_line_filter_wizard: improve module description
Update version number
2019-05-13 20:02:52 +02:00
Alexis de Lattre
a61f1e385a account_move_line_filter_wizard: improve to add direct access to general ledger or open items report 2019-05-13 18:56:13 +02:00
Alexis de Lattre
d9c340e513 stock_usability: add link to Inventory Lines on product form view (menu "Action") 2019-05-13 16:10:20 +02:00
Alexis de Lattre
ba68bbecda crm_partner_prospect: avoid post-write -> improve perf 2019-05-10 23:57:00 +02:00
Alexis de Lattre
794d28468b Fix context in action for prospect 2019-05-10 23:49:46 +02:00
Alexis de Lattre
2dad83b91e Improve search and pivot view of account.move.line 2019-04-29 18:39:16 +02:00
Alexis de Lattre
aa2f90f0d1 account_usability: add script _fix_debit_credit_round_bug 2019-04-06 00:48:26 +02:00
Alexis de Lattre
16e550d0d5 base_usability: Add new options to _display_full_address() 2019-04-01 18:11:17 +02:00
Pierrick Brun
5a95b771f9 Merge pull request #87 from akretion/10-account_view_search_deb_cred
[IMP] 10.0 account.move.line: Add filter on both debit and credit
2019-04-01 11:47:53 +02:00
Pierrick Brun
d4d0666349 [IMP] make PDF reports horizontal
Pas pour la balance
2019-03-20 10:45:21 +01:00
Alexis de Lattre
8180da1d6e FIX crash in account_invoice_margin 2019-03-03 21:45:24 +01:00
Alexis de Lattre
aec54baec5 Improve list view of res.partner.bank
Improve name_get of res.bank
Add filter on product categ on pos_sale_report
2019-02-27 17:30:36 +01:00
Alexis de Lattre
e33bee1f05 account_usability: improve create group script 2019-02-27 10:47:08 +01:00
Alexis de Lattre
6f0f0b0a0d Private methods for scripts
Use sudo() in scripts that require admin access
2019-02-25 22:02:05 +01:00
Sébastien BEAU
6ce749c70f [FIX] fix helper 2019-02-25 15:57:56 +01:00
Sébastien BEAU
27fa44c68f [IMP] by default do not send an email when user_id is fill on object 2019-02-19 23:05:32 +01:00
Alexis de Lattre
6ecc01c108 On product.template+product.categ, all accounting fields are groups="account.group_account_invoice" 2019-02-15 14:53:39 +01:00
Alexis de Lattre
b2cda5e522 account_usability: remove access to form view of invoice lines via dedicated action until we have a proper readonly system for non-draft invoices 2019-02-15 00:13:34 +01:00
Alexis de Lattre
351fc2038d account_usability: hide "Accounting > Configuration > Financial Reports" 2019-02-14 20:53:14 +01:00
Alexis de Lattre
9505c2d5cd purchase_usability: purchase report is now a 3rd level menu entry like almost all clickable menu entries 2019-02-14 20:48:30 +01:00
Alexis de Lattre
e9b56ce5dd Add multi-company ir.rule for crm.lead 2019-02-14 20:34:21 +01:00
Alexis de Lattre
55b044a1fc Add module account_financial_report_qweb_usability 2019-02-14 18:00:55 +01:00
Alexis de Lattre
8a8780b810 account_usability: fix onchange on amount_currency: invert debit and credit 2019-02-14 17:43:48 +01:00
Alexis de Lattre
6b3d2263c7 account_usability: Improve onchange for amount_currency 2019-02-14 17:31:17 +01:00
Hpar
2b89e57d72 Merge pull request #84 from akretion/10.0-fix-purchase_order_buyer
[10][FIX] purchase_ordre_buyer
2019-02-13 11:17:41 +01:00
hparfr
9477219b71 Call super in onchanger() 2019-02-13 11:15:59 +01:00
Alexis de Lattre
d191aadc4e Add button to view the items of a pricelist full screen, to be able to search on items 2019-02-05 23:15:24 +01:00
Alexis de Lattre
ff97e81105 Hide default_credit and default_debit, now that it works well 2019-02-04 18:06:41 +01:00
Alexis de Lattre
286b80bf95 stock_usability: code cleanup 2019-02-04 15:33:08 +01:00
Alexis de Lattre
5775ad651f Fix string 2019-01-30 19:39:23 +01:00
Adrien Peiffer
bec0a50a72 [IMP] Journal entry should be visible in done state. (#50) 2019-01-29 10:50:24 +01:00
Florent Jouatte
99cacec19d [FIX] 'hr_expense_usability.generic_private_car_expense' does not exist (#76)
* [FIX] 'hr_expense_usability.generic_private_car_expense' does not exist

* [FIX] wrong xmlid, replace the module name by the right one
2019-01-29 10:47:31 +01:00
Pierrick Brun
1913556368 account.move.line: Add filter on both debit and credit (#82)
picked from @alexis-via (d4fcaa7d14)
2019-01-15 10:09:17 +01:00
Pierrick brun
29481d91b7 account.move.line: Add filter on both debit and credit
picked from @alexis-via (d4fcaa7d14)
2019-01-14 16:28:04 +01:00
Sébastien BEAU
ea402d4cbe [REF] refactor the code in order to split it in several file 2019-01-11 11:10:45 +01:00
Sébastien BEAU
a2564f48a3 [IMP] add record_id on mail.message to be able to access to the record 2019-01-11 10:56:31 +01:00
Sébastien BEAU
fc45c44280 [IMP] add some extra style css support and add a debugger mode. Update readme 2019-01-08 23:27:23 +01:00
Sébastien BEAU
a370c9100c [IMP] remove the fucking auto_delete!!! 2019-01-08 23:26:12 +01:00
Sébastien BEAU
e5d6ef4bad [REF] refactor the code to make it simplifier and avoid hacking the _notify method 2019-01-08 23:24:59 +01:00
David Beal
c3f1d30bdd Merge pull request #81 from akretion/10.0-sale_partner_shipping_filter
10.0 sale partner shipping filter
2019-01-04 12:48:23 +01:00
Pierrick brun
6405be3fcf [MIG] 10.0 2019-01-04 11:11:39 +01:00
chafique-delli
7a65c63f99 replace partner_parent_id by commercial_partner_id 2019-01-04 11:00:33 +01:00
chafique-delli
13d8100d98 fix after @bealdav's comment 2019-01-04 11:00:33 +01:00
chafique-delli
0151c6a6e3 domain improvement 2019-01-04 11:00:33 +01:00
chafique-delli
ea5fdcf4df fix domain 2019-01-04 11:00:33 +01:00
chafique-delli
4ff06be1a6 add sale_partner_shipping_filter_with_customer module 2019-01-04 11:00:33 +01:00
David Beal
6c3c6cd43b Merge pull request #72 from akretion/10-fix-py3o-lines-sorting
[FIX] keep the order line sorted when creating the layout_lines dict
2019-01-02 18:56:18 +01:00
Benoît Guillot
3fef559aac [IMP] add domain on partner_id field in account_move and account_move_line search views (#73) 2019-01-02 18:55:15 +01:00
Alexis de Lattre
75d7b7eac4 Add script fix_invoice_attachment_filename 2018-12-20 11:20:31 +01:00
Sébastien BEAU
4a2fff177c [IMP] add balance in view 2018-12-18 23:25:23 +01:00
Sébastien BEAU
e83238becd [IMP] hide odoo report menu and hide the button_cancel on bacnk_statement as the code do not allow to cancel it 2018-12-18 19:53:39 +01:00
Alexis de Lattre
8bebd1e2ef Add @api.model on method that should use it
Improve log msg
2018-12-17 11:17:43 +01:00
Alexis de Lattre
b23f03f79d Add src and dest location on prodlot selection popup 2018-12-05 21:23:43 +01:00
Mourad EL HADJ MIMOUNE
dc8363d6d1 [FIX] add start_date,end_date in statement.display_name 2018-12-04 16:39:57 +01:00
Alexis de Lattre
ad850024ec Script for account.group now works in multi-company envir 2018-12-04 16:33:23 +01:00
Sébastien BEAU
03564a20b2 [IMP] add readme, remove auto following when sending an email, use light version of email notification to avoid injecting useless link in the mail sent 2018-12-04 11:55:57 +01:00
Alexis de Lattre
c1618166fb Improve account group generation 2018-11-29 21:57:19 +01:00
Alexis de Lattre
cf2464dfa4 Add module sale_order_full_dropship 2018-11-29 12:34:34 +01:00
Alexis de Lattre
80191002b8 Fix typo and code cleanup 2018-11-28 17:15:43 +01:00
Alexis de Lattre
694c800df3 Fix visibility of invoice_print button on invoice form 2018-11-28 16:04:18 +01:00
Alexis de Lattre
bdf51029c7 Add script to create account groups 2018-11-22 21:25:40 +01:00
Alexis de Lattre
478ab1fc2b account_usability: improve display of reconcile information, in particular partial reconcile
Warning: on existing big databases, this upgrade will take a long time
because there is a new computed stored field on account.move.line. But it is
required to keep good perfs on tree view of move lines.
2018-11-22 16:26:27 +01:00
Alexis de Lattre
362dba5f90 Restore drill-through on sale and invoice reports 2018-11-21 18:15:25 +01:00
Alexis de Lattre
ac2b70b66e Add margin in sale.report 2018-11-21 16:44:05 +01:00
Alexis de Lattre
b75a2e47a2 Add margin in invoice report
Consequence: no more need for module account_invoice_margin_report
2018-11-21 16:22:10 +01:00
Pierrick Brun
3f73f15e4a [ADD] confirm on reset_real_qty for stock.inventory 2018-11-20 15:27:01 +01:00
Alexis de Lattre
c196343ec0 Improve usability of account.move creation/edition
Default value for account_id, debit, credit, similar to v8 behavior
2018-11-19 19:21:23 +01:00
Alexis de Lattre
7d359d6730 Fix typo 2018-11-19 11:09:37 +01:00
Alexis de Lattre
904bf6c4f4 Cut name_get() of invoice if too long (which screws-up the invoice form view because of the ariane thread at the top) 2018-11-19 10:52:04 +01:00
Alexis de Lattre
e13a2aba3d Remove menu entry for analytic tags
Hide field tag_ids on form view of analytic accounts
2018-11-12 18:27:02 +01:00
Alexis de Lattre
8cb880771e Add module stock_user_default_warehouse_mrp
Fix module stock_user_default_warehouse_purchase
2018-11-02 11:54:21 +01:00
Alexis de Lattre
087bb1fde2 MRP production form: move src/dest loc to the top 2018-10-26 15:46:17 +02:00
Pierrick Brun
806b1b4a86 Merge pull request #68 from yvaucher/10.0-account_invoice_update_wizard-analytic
[10.0] account_invoice_update_wizard analytic fields
2018-10-22 13:56:46 +02:00
Yannick Vaucher
23519f4027 Merge pull request #1 from akretion/10.0-account_invoice_update_wizard-analytic
[FIX] only show analytics to users in group
2018-10-19 14:57:56 +02:00
Pierrick Brun
b54ec10f10 Merge pull request #63 from akretion/10.0-mig-product_search_supplier_code
[MIG] 10.0 product_search_supplier_code
2018-10-17 10:08:06 +02:00
Benoit
118dd2a5c0 [FIX] keep invoice lines sorted when creating the layout_lines dict 2018-09-28 17:42:31 +02:00
Hpar
0afe9a39a6 Merge pull request #57 from akretion/10-product_unit_manager_group-migration
10 product unit manager group migration
2018-09-25 11:56:47 +02:00
Hpar
159f163da5 Merge pull request #61 from akretion/10-add-purchase_buyer
Add purchase buyer module
2018-09-21 14:30:29 +02:00
Hpar
d0620a4c83 Update README.rst 2018-09-21 14:30:18 +02:00
Alexis de Lattre
5d2d5b1e63 Revert my previous commit: use partner_bank_active instead
The module partner_bank_active is avail in OCA/partner-contact
2018-09-18 23:19:09 +02:00
Alexis de Lattre
ab1144850b Fix crash when several quotes are linked to the opportunity 2018-09-18 23:03:21 +02:00
Alexis de Lattre
4995403bf5 Add active field on res.partner.bank 2018-09-18 21:32:16 +02:00
Alexis de Lattre
a415744f11 Add widget=handle on sequence of res.partner.bank 2018-09-17 11:18:01 +02:00
Benoit
c943b4cd33 [FIX] keep the order line sorted when creating the layout_lines dict 2018-09-14 17:46:15 +02:00
Alexis de Lattre
8800b94e5b Fix bad port of name_get() of account.analytic.account to v10 2018-09-13 11:56:37 +02:00
beau sebastien
ca5238a03c Merge pull request #70 from akretion/10.0-better-wizard-for-mail-test
[IMP] improve the wizard for testing email, allow to search on object and to send email for real check
2018-09-04 15:54:59 +02:00
Sébastien BEAU
5f15d83c3c [IMP] improve the wizard for testing email, allow to search on object and to send email for real check 2018-09-04 15:50:02 +02:00
Alexis de Lattre
6370dc0ec8 Port base_user_auth_log to v10 2018-09-02 22:39:49 +02:00
Alexis de Lattre
0d27c0a830 Add module base_user_auth_log 2018-09-02 22:21:05 +02:00
Alexis de Lattre
5f6107f2e8 Add a patch to have analytic in case of writeoff in the register payment
wizard
2018-07-23 16:20:38 +02:00
Alexis de Lattre
ebb8f1ad86 carrier_id not readonly on done picking (add tracking on it)
fix for formatLang inherit in base_usability
2018-07-18 14:27:21 +02:00
Alexis de Lattre
04118bbf46 account_usability: Add copy=False on some fields 2018-07-16 17:16:15 +02:00
Alexis de Lattre
9cba31b68a Show title not only on Contacts 2018-07-13 15:43:46 +02:00
Alexis de Lattre
21d1454ab9 Use untaxed amount in name_get of purchase orders
Add sum for qty in operation lines
2018-07-12 23:53:26 +02:00
Alexis de Lattre
d4e673103e Show 'base' field in tax lines on invoice form view 2018-07-09 17:44:55 +02:00
Alexis de Lattre
9ebf7cdb4c FIX my previous commit: related_sudo -> compute_sudo 2018-07-09 11:43:22 +02:00
Alexis de Lattre
f34a731d95 Add related_sudo where it may be needed
PEP8 fix
2018-07-09 10:41:53 +02:00
Alexis de Lattre
6ef322be4c Improve invoice line view 2018-07-06 22:26:38 +02:00
Alexis de Lattre
dd15e3d194 Add search on supplier on product search view
Move margin fields to sale order line form view (instead of tree view)
2018-07-06 19:33:35 +02:00
Pierrick Brun
e3fc2764fb [MIG] 10.0 product_search_supplier_code 2018-04-26 14:55:26 +02:00
hparfr
f0bb02edf1 Add purchase buyer module 2018-04-11 10:49:41 +02:00
hparfr
dc05ed3b5d Migration to v10 2018-02-14 11:28:03 +01:00
chafique-delli
2c1b8fe657 Moves product_unit_manager_group from 8.0 2018-02-14 11:27:22 +01:00
241 changed files with 7160 additions and 642 deletions

View File

@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import report
from . import wizard

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# © 2015-2016 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': 'Account Financial Report Qweb Usability',
'version': '10.0.1.0.0',
'category': 'Accounting & Finance',
'license': 'AGPL-3',
'summary': 'Small usability enhancements in account_financial_report_qweb module',
'description': """
Account Financial Report Usability
==================================
The usability enhancements include:
TODO
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': [
'account_financial_report_qweb',
],
'data': [
'views/reports.xml',
'views/layouts.xml',],
'installable': True,
}

View File

@@ -0,0 +1,3 @@
.list_table, .data_table, .totals_table, .list_table .act_as_row {
font-size:15px;
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_specific" inherit_id="account_financial_report_qweb.assets_specific">
<xpath expr="." position="inside">
<link href="/account_financial_report_qweb_usability/static/src/css/reports.css" rel="stylesheet"/>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="report_qweb_paperformat_horizontal" model="report.paperformat">
<field name="name">Account financial report qweb horizontal paperformat</field>
<field name="default" eval="True"/>
<field name="format">custom</field>
<field name="page_height">297</field>
<field name="page_width">210</field>
<field name="orientation">Landscape</field>
<field name="margin_top">12</field>
<field name="margin_bottom">8</field>
<field name="margin_left">5</field>
<field name="margin_right">5</field>
<field name="header_line" eval="False"/>
<field name="header_spacing">10</field>
<field name="dpi">110</field>
</record>
<record id="account_financial_report_qweb.action_report_general_ledger_qweb" model="ir.actions.report.xml">
<field name="paperformat_id" ref="report_qweb_paperformat_horizontal"/>
</record>
<record id="account_financial_report_qweb.action_report_journal_qweb" model="ir.actions.report.xml">
<field name="paperformat_id" ref="report_qweb_paperformat_horizontal"/>
</record>
<record id="account_financial_report_qweb.action_report_open_items_qweb" model="ir.actions.report.xml">
<field name="paperformat_id" ref="report_qweb_paperformat_horizontal"/>
</record>
</odoo>

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import general_ledger_wizard
from . import open_items_wizard

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# 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 fields, models
class GeneralLedgerReportWizard(models.TransientModel):
_inherit = 'general.ledger.report.wizard'
foreign_currency = fields.Boolean(default=False)
def onchange_partner_ids(self):
# Neutralize native onchange method
return

View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# 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 fields, models
class OpenItemsReportWizard(models.TransientModel):
_inherit = "open.items.report.wizard"
foreign_currency = fields.Boolean(default=False)

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import account_invoice
from . import account_invoice_report

View File

@@ -83,8 +83,9 @@ class AccountInvoiceLine(models.Model):
std_price = pp.standard_price
inv_uom_id = vals.get('uom_id')
if inv_uom_id and inv_uom_id != pp.uom_id.id:
std_price = self.env['product.uom']._compute_price(
pp.uom_id.id, std_price, inv_uom_id)
inv_uom = self.env['product.uom'].browse(inv_uom_id)
std_price = pp.uom_id._compute_price(
std_price, inv_uom)
vals['standard_price_company_currency'] = std_price
return super(AccountInvoiceLine, self).create(vals)

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright 2018 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, api
class AccountInvoiceReport(models.Model):
_inherit = 'account.invoice.report'
margin = fields.Float(string='Margin', readonly=True)
# why digits=0 ??? Why is it like that in the native "account" module
user_currency_margin = fields.Float(
string="Margin", compute='_compute_user_currency_margin', digits=0)
_depends = {
'account.invoice': [
'account_id', 'amount_total_company_signed',
'commercial_partner_id', 'company_id',
'currency_id', 'date_due', 'date_invoice', 'fiscal_position_id',
'journal_id', 'partner_bank_id', 'partner_id', 'payment_term_id',
'residual', 'state', 'type', 'user_id',
],
'account.invoice.line': [
'account_id', 'invoice_id', 'price_subtotal', 'product_id',
'quantity', 'uom_id', 'account_analytic_id',
'margin_company_currency',
],
'product.product': ['product_tmpl_id'],
'product.template': ['categ_id'],
'product.uom': ['category_id', 'factor', 'name', 'uom_type'],
'res.currency.rate': ['currency_id', 'name'],
'res.partner': ['country_id'],
}
@api.depends('currency_id', 'date', 'margin')
def _compute_user_currency_margin(self):
context = dict(self._context or {})
user_currency_id = self.env.user.company_id.currency_id
currency_rate_id = self.env['res.currency.rate'].search([
('rate', '=', 1),
'|',
('company_id', '=', self.env.user.company_id.id),
('company_id', '=', False)], limit=1)
base_currency_id = currency_rate_id.currency_id
ctx = context.copy()
for record in self:
ctx['date'] = record.date
record.user_currency_margin = base_currency_id.with_context(
ctx).compute(record.margin, user_currency_id)
# TODO check for refunds
def _sub_select(self):
select_str = super(AccountInvoiceReport, self)._sub_select()
select_str += ", SUM(ail.margin_company_currency) AS margin"
return select_str
def _select(self):
select_str = super(AccountInvoiceReport, self)._select()
select_str += ", sub.margin AS margin"
return select_str

View File

@@ -12,17 +12,21 @@
<field name="model">account.invoice.line</field>
<field name="inherit_id" ref="account.view_invoice_line_form"/>
<field name="arch" type="xml">
<field name="discount" position="after">
<field name="company_id" position="after">
<field name="standard_price_company_currency"
groups="account.group_account_user"/>
groups="base.group_no_one"/>
<field name="standard_price_invoice_currency"
widget="monetary"
options="{'currency_field': 'currency_id'}"
groups="account.group_account_user"/>
groups="base.group_no_one"/>
<field name="margin_invoice_currency"
groups="account.group_account_user"/>
groups="base.group_no_one"/>
<field name="margin_company_currency"
groups="account.group_account_user"/>
groups="base.group_no_one"/>
<label for="margin_rate" groups="base.group_no_one"/>
<div name="margin_rate" groups="base.group_no_one">
<field name="margin_rate" class="oe_inline"/> %
</div>
</field>
</field>
</record>
@@ -34,18 +38,10 @@
<field name="arch" type="xml">
<field name="move_id" position="after">
<field name="margin_invoice_currency"
string="Margin" groups="account.group_account_user"/>
string="Margin" groups="base.group_no_one"/>
<field name="margin_company_currency"
groups="account.group_account_user"/>
groups="base.group_no_one"/>
</field>
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='price_subtotal']" position="after">
<field name="standard_price_invoice_currency" groups="base.group_no_one" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="standard_price_company_currency" groups="base.group_no_one" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
<field name="margin_invoice_currency" groups="base.group_no_one"/>
<field name="margin_company_currency" groups="base.group_no_one"/>
<field name="margin_rate" groups="base.group_no_one"/>
<field name="company_currency_id" invisible="1"/>
</xpath>
</field>
</record>

View File

@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Account Invoice Margin Report module for Odoo
# Copyright (C) 2015 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Account Invoice Margin Report',
'version': '0.1',
'category': 'Accounting & Finance',
'license': 'AGPL-3',
'summary': 'Add margin measure in Invoices Analysis',
'description': """
This module adds the measure *Margin* in the Invoices Analysis pivot table. It is in a separate module because it depends on the module *bi_invoice_company_currency* (in which I re-wrote the Invoice Analysis pivot table).
This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['account_invoice_margin', 'bi_invoice_company_currency'],
'data': [],
'installable': False,
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import invoice_report

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Account Invoice Margin Report module for Odoo
# Copyright (C) 2015 Akretion (http://www.akretion.com/)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields
import openerp.addons.decimal_precision as dp
class AccountInvoiceReportBi(models.Model):
_inherit = "account.invoice.report.bi"
margin_company_currency = fields.Float(
string='Margin', readonly=True,
digits=dp.get_precision('Account'))
def _select(self):
select = super(AccountInvoiceReportBi, self)._select()
select += """
, sum(ail.margin_company_currency) AS margin_company_currency
"""
return select

View File

@@ -5,7 +5,7 @@
{
'name': 'Account Move Line Filter Wizard',
'version': '10.0.1.0.0',
'version': '10.0.2.0.0',
'category': 'Accounting',
'license': 'AGPL-3',
'summary': 'Easy and fast access to the details of an account',
@@ -13,13 +13,21 @@
Account Move Line Filter Wizard
===============================
This module adds a wizard in Accounting > ... >
This module adds a *Show Account* wizard under *Accounting > Adviser*. This wizard gives an easy and fast access to the details of an account:
* access to the General Ledger Report,
* access to the Open Items Report (if the user selected a reconciliable account and the Unreconciled filter),
* access to the Journal Items view.
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['account_usability'],
'depends': [
'account_usability',
'account_financial_report_qweb',
'account_fiscal_year',
],
'data': ['wizard/account_move_line_filter_view.xml'],
'installable': True,
}

View File

@@ -3,13 +3,16 @@
# @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, api
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class AccountMoveLineFilterWizard(models.TransientModel):
_name = 'account.move.line.filter.wizard'
_description = 'Wizard for easy and fast access to account move lines'
date_range_id = fields.Many2one(
'date.range', string='Date Range (only for General Ledger)')
partner_id = fields.Many2one(
'res.partner', string='Partner', domain=[('parent_id', '=', False)])
account_id = fields.Many2one(
@@ -18,11 +21,32 @@ class AccountMoveLineFilterWizard(models.TransientModel):
account_reconcile = fields.Boolean(
related='account_id.reconcile', readonly=True)
reconcile = fields.Selection([
('unreconciled', 'Unreconciled'),
('unreconciled', 'Unreconciled or Partially Reconciled'),
('reconciled', 'Fully Reconciled'),
# ('partial_reconciled', 'Partially Reconciled'),
], string='Reconciliation Filter')
@api.model
def default_get(self, fields_list):
res = super(AccountMoveLineFilterWizard, self).default_get(fields_list)
today = fields.Date.context_today(self)
fy_type_id = self.env.ref('account_fiscal_year.fiscalyear').id
dro = self.env['date.range']
date_range = dro.search([
('type_id', '=', fy_type_id),
('company_id', '=', self.env.user.company_id.id),
('date_start', '<=', today),
('date_end', '>=', today)
], limit=1)
if not date_range:
date_range = dro.search([
('type_id', '=', fy_type_id),
('company_id', '=', self.env.user.company_id.id),
], order='date_start desc', limit=1)
if date_range:
res['date_range_id'] = date_range.id
return res
@api.onchange('partner_id')
def partner_id_change(self):
if self.partner_id:
@@ -44,3 +68,33 @@ class AccountMoveLineFilterWizard(models.TransientModel):
if self.reconcile:
action['context']['search_default_%s' % self.reconcile] = True
return action
def show_report_general_ledger(self):
self.ensure_one()
if self.account_reconcile:
assert self.reconcile != 'unreconciled'
if not self.date_range_id:
raise UserError(_(
"Select a date range to show the General Ledger report."))
wvals = {
'account_ids': [(6, 0, [self.account_id.id])],
'date_from': self.date_range_id.date_start,
'date_to': self.date_range_id.date_end,
}
if self.partner_id:
wvals['partner_ids'] = [(6, 0, [self.partner_id.id])]
wiz = self.env['general.ledger.report.wizard'].create(wvals)
action = wiz.button_export_html()
return action
def show_report_open_items(self):
self.ensure_one()
assert self.account_reconcile and self.reconcile == 'unreconciled'
wvals = {
'account_ids': [(6, 0, [self.account_id.id])],
}
if self.partner_id:
wvals['partner_ids'] = [(6, 0, [self.partner_id.id])]
wiz = self.env['open.items.report.wizard'].create(wvals)
action = wiz.button_export_html()
return action

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016-2018 Akretion (http://www.akretion.com/)
Copyright (C) 2016-2019 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).
-->
@@ -12,16 +12,19 @@
<field name="name">account_move_line_filter_wizard_form</field>
<field name="model">account.move.line.filter.wizard</field>
<field name="arch" type="xml">
<form string="Account Move Lines">
<form string="Show Account">
<group name="filters" string="Filters">
<field name="partner_id"/>
<field name="account_id"/>
<field name="account_reconcile" invisible="1"/>
<field name="reconcile"
attrs="{'invisible': [('account_reconcile', '!=', True)]}"/>
<field name="date_range_id" attrs="{'invisible': [('account_reconcile', '=', True), ('reconcile', '=', 'unreconciled')]}"/>
</group>
<footer>
<button type="object" name="go" string="Go" class="btn-primary"/>
<button type="object" name="show_report_general_ledger" string="General Ledger Report" class="btn-primary" attrs="{'invisible': [('account_reconcile', '=', True), ('reconcile', '=', 'unreconciled')]}"/>
<button type="object" name="show_report_open_items" string="Open Items Report" class="btn-primary" attrs="{'invisible': ['|', ('account_reconcile', '=', False), ('reconcile', '!=', 'unreconciled')]}"/>
<button type="object" name="go" string="Journal Items" class="btn-primary"/>
<button special="cancel" string="Cancel" class="btn-default"/>
</footer>
</form>
@@ -29,7 +32,7 @@
</record>
<record id="account_move_line_filter_wizard_action" model="ir.actions.act_window">
<field name="name">Journal Items of Account</field>
<field name="name">Show Account</field>
<field name="res_model">account.move.line.filter.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>

View File

@@ -19,7 +19,7 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['account'],
'depends': ['account', 'base_usability'],
'data': ['account_view.xml'],
'installable': True,
}

View File

@@ -42,5 +42,20 @@
</field>
</record>
<!-- ANALYTIC ACCOUNT -->
<record id="view_account_analytic_account_form" model="ir.ui.view">
<field name="name">account_no_analytic_tags.analytic.account.form</field>
<field name="model">account.analytic.account</field>
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
<field name="arch" type="xml">
<field name="tag_ids" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>
<record id="account.account_analytic_tag_menu" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
</record>
</odoo>

View File

@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from . import account
from . import account_invoice_report
from . import partner
from . import product
from . import wizard

View File

@@ -31,15 +31,18 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
'website': 'http://www.akretion.com',
'depends': [
'account',
'base_view_inheritance_extension',
'base_usability', # needed only to access base_usability.group_nobody
# in v12, I may create a module only for group_nobody
],
'data': [
'account_view.xml',
'account_report.xml',
'account_invoice_report_view.xml',
'partner_view.xml',
'product_view.xml',
'wizard/account_invoice_mark_sent_view.xml',
'wizard/account_move_backtodraft_view.xml',
],
'installable': True,
}

View File

@@ -4,7 +4,8 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo.tools import float_compare, float_is_zero
from odoo.tools import float_compare, float_is_zero, float_round
from odoo.tools.misc import formatLang
from odoo.exceptions import UserError, ValidationError
from odoo import SUPERUSER_ID
import logging
@@ -43,7 +44,6 @@ class AccountInvoice(models.Model):
compute='_compute_has_attachment',
search='_search_has_attachment', readonly=True)
@api.multi
def _compute_has_discount(self):
prec = self.env['decimal.precision'].precision_get('Discount')
for inv in self:
@@ -57,11 +57,11 @@ class AccountInvoice(models.Model):
def _compute_has_attachment(self):
iao = self.env['ir.attachment']
for inv in self:
if iao.search([
if iao.search_count([
('res_model', '=', 'account.invoice'),
('res_id', '=', inv.id),
('type', '=', 'binary'),
('company_id', '=', inv.company_id.id)], limit=1):
('company_id', '=', inv.company_id.id)]):
inv.has_attachment = True
else:
inv.has_attachment = False
@@ -78,21 +78,38 @@ class AccountInvoice(models.Model):
res = [('id', value and 'in' or 'not in', att_inv_ids.keys())]
return res
# when you have an invoice created from a lot of sale orders, the 'name'
# field is very large, which makes the name_get() of that invoice very big
# which screws-up the form view of that invoice because of the link at the
# top of the screen
# That's why we have to cut the name_get() when it's too long
def name_get(self):
old_res = super(AccountInvoice, self).name_get()
res = []
for old_re in old_res:
name = old_re[1]
if name and len(name) > 100:
# nice cut
name = u'%s ...' % ', '.join(name.split(', ')[:3])
# if not enough, hard cut
if len(name) > 120:
name = u'%s ...' % old_re[1][:120]
res.append((old_re[0], name))
return res
# I really hate to see a "/" in the 'name' field of the account.move.line
# generated from customer invoices linked to the partners' account because:
# 1) the label of an account move line is an important field, we can't
# write a rubbish '/' in it !
# 2) the 'name' field of the account.move.line is used in the overdue
# letter, and '/' is not meaningful for our customer !
# generated from customer invoices linked to the partners' account because
# the label of an account move line is an important field, we can't
# write a rubbish '/' in it !
# On a related topic, you should also consider to use this PR:
# https://github.com/OCA/account-invoicing/pull/882
@api.multi
def action_move_create(self):
res = super(AccountInvoice, self).action_move_create()
for inv in self:
self._cr.execute(
"UPDATE account_move_line SET name= "
"CASE WHEN name='/' THEN %s "
"ELSE %s||' - '||name END "
"WHERE move_id=%s", (inv.number, inv.number, inv.move_id.id))
"UPDATE account_move_line SET name=%s "
"WHERE move_id=%s AND name='/'", (inv.number, inv.move_id.id))
self.invalidate_cache()
return res
@@ -102,6 +119,38 @@ class AccountInvoice(models.Model):
lines.unlink()
return True
@api.model
def _fix_invoice_attachment_filename(self):
# This script is designed to fix attachment of invoices
# badly generated by Odoo v8. I found this problem in Nov 2018 at
# Encres Dubuit when investigating a bug where Odoo would create a
# new attachment when printing an old invoice that already had the
# PDF of the invoice as attachment
logger.info('START fix customer invoice attachment filename')
# Run this script as admin to fix problem in all companies
self = self.sudo()
attachs = self.env['ir.attachment'].search([
('res_model', '=', 'account.invoice'),
('res_id', '!=', False),
('type', '=', 'binary'),
('name', '=like', 'INV%.pdf'),
('datas_fname', '=like', 'INV%.pdf.pdf')])
for attach in attachs:
inv = self.browse(attach.res_id)
if inv.type in ('out_invoice', 'out_refund'):
attach.datas_fname = attach.name
logger.info(
'Fixed field datas_fname of attachment ID %s name %s',
attach.id, attach.name)
logger.info('END fix customer invoice attachment filename')
@api.multi
def invoice_print(self):
# Inherit a native method without calling super()
# Don't mark invoice as 'sent' when you just click on 'Print Invoice'
self.ensure_one()
return self.env['report'].get_action(self, 'account.report_invoice')
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
@@ -114,7 +163,7 @@ class AccountInvoiceLine(models.Model):
related='invoice_id.date_invoice', store=True, readonly=True)
commercial_partner_id = fields.Many2one(
related='invoice_id.partner_id.commercial_partner_id',
store=True, readonly=True)
store=True, readonly=True, compute_sudo=True)
state = fields.Selection(
related='invoice_id.state', store=True, readonly=True,
string='Invoice State')
@@ -126,6 +175,15 @@ class AccountInvoiceLine(models.Model):
class AccountJournal(models.Model):
_inherit = 'account.journal'
hide_bank_statement_balance = fields.Boolean(
string='Hide Bank Statement Balance',
help="You may want to enable this option when your bank "
"journal is generated from a bank statement file that "
"doesn't handle start/end balance (QIF for instance) and "
"you don't want to enter the start/end balance manually: it "
"will prevent the display of wrong information in the accounting "
"dashboard and on bank statements.")
@api.multi
@api.depends(
'name', 'currency_id', 'company_id', 'company_id.currency_id', 'code')
@@ -144,20 +202,6 @@ class AccountJournal(models.Model):
res.append((journal.id, name))
return res
# Also search on start of 'code', not only on 'name'
@api.model
def name_search(
self, name='', args=None, operator='ilike', limit=80):
if args is None:
args = []
if name:
jrls = self.search(
[('code', '=ilike', name + '%')] + args, limit=limit)
if jrls:
return jrls.name_get()
return super(AccountJournal, self).name_search(
name=name, args=args, operator=operator, limit=limit)
@api.constrains('default_credit_account_id', 'default_debit_account_id')
def _check_account_type_on_bank_journal(self):
bank_acc_type = self.env.ref('account.data_account_type_liquidity')
@@ -197,19 +241,19 @@ class AccountAccount(models.Model):
return super(AccountAccount, self).name_get()
# https://github.com/odoo/odoo/issues/23040
def fix_bank_account_types(self):
aao = self.env['account.account']
@api.model
def _fix_bank_account_types(self):
companies = self.env['res.company'].search([])
if len(companies) > 1 and self.env.user.id != SUPERUSER_ID:
raise UserError(
"In multi-company setups, you should run this "
"script as admin user")
logger.info(
"Multi-company setup detected, running script with sudo ")
self = self.sudo()
logger.info("START the script 'fix bank and cash account types'")
bank_type = self.env.ref('account.data_account_type_liquidity')
asset_type = self.env.ref('account.data_account_type_current_assets')
journals = self.env['account.journal'].search(
[('type', 'in', ('bank', 'cash'))], order='company_id')
journal_accounts_bank_type = aao
journal_accounts_bank_type = self
for journal in journals:
for account in [
journal.default_credit_account_id,
@@ -223,9 +267,9 @@ class AccountAccount(models.Model):
account.company_id.display_name, account.code)
if account not in journal_accounts_bank_type:
journal_accounts_bank_type += account
accounts = aao.search([
accounts = self.search([
('user_type_id', '=', bank_type.id)], order='company_id, code')
for account in aao.search([('user_type_id', '=', bank_type.id)]):
for account in accounts:
if account not in journal_accounts_bank_type:
account.user_type_id = asset_type.id
logger.info(
@@ -234,6 +278,49 @@ class AccountAccount(models.Model):
logger.info("END of the script 'fix bank and cash account types'")
return True
@api.model
def _create_account_groups(self, level=2, name_prefix=u'Comptes '):
'''Should be launched by a script. Make sure the account_group module is installed
(the account_usability module doesn't depend on it currently'''
assert level >= 1
assert isinstance(level, int)
companies = self.env['res.company'].search([])
if len(companies) > 1:
logger.info(
'Multi-company detected: running script create_account_groups '
'as admin')
self = self.sudo()
ago = self.env['account.group']
groups = ago.search([])
if groups:
raise UserError(_("Some account groups already exists"))
accounts = self.search([])
struct = {'childs': {}}
for account in accounts:
if len(account.code) <= level:
logger.warning(
"Account '%s' in company '%s' is smaller than "
"level (%d).",
account.display_name, account.company_id.display_name,
level)
continue
n = 1
parent = struct
gparent = False
while n <= level:
group_code = account.code[:n]
if group_code not in parent['childs']:
new_group = ago.create({
'name': u'%s%s' % (name_prefix or '', group_code),
'code_prefix': group_code,
'parent_id': gparent and gparent.id or False,
})
parent['childs'][group_code] = {'obj': new_group, 'childs': {}}
parent = parent['childs'][group_code]
gparent = parent['obj']
n += 1
account.group_id = gparent.id
class AccountAnalyticAccount(models.Model):
_inherit = 'account.analytic.account'
@@ -243,9 +330,7 @@ class AccountAnalyticAccount(models.Model):
if self._context.get('analytic_account_show_code_only'):
res = []
for record in self:
res.append((
record.id,
record.code or record._get_one_full_name(record)))
res.append((record.id, record.code or record.name))
return res
else:
return super(AccountAnalyticAccount, self).name_get()
@@ -265,6 +350,81 @@ class AccountMove(models.Model):
# By default, we can still modify "ref" when account move is posted
# which seems a bit lazy for me...
ref = fields.Char(states={'posted': [('readonly', True)]})
date = fields.Date(copy=False)
default_account_id = fields.Many2one(
related='journal_id.default_debit_account_id', readonly=True)
default_credit = fields.Float(
compute='_compute_default_credit_debit', readonly=True)
default_debit = fields.Float(
compute='_compute_default_credit_debit', readonly=True)
@api.depends('line_ids.credit', 'line_ids.debit')
def _compute_default_credit_debit(self):
for move in self:
total_debit = total_credit = default_debit = default_credit = 0.0
for l in move.line_ids:
total_debit += l.debit
total_credit += l.credit
# I could use float_compare, but I don't think it's really needed
# in this context
if total_debit > total_credit:
default_credit = total_debit - total_credit
else:
default_debit = total_credit - total_debit
move.default_credit = default_credit
move.default_debit = default_debit
@api.model
def _fix_debit_credit_round_bug(self):
logger.info('START script _fix_debit_credit_round_bug')
moves = self.sudo().search([]) # sudo to search in all companies
bug_move_ids = []
for move in moves:
buggy = False
for l in move.line_ids:
if not float_is_zero(l.debit, precision_digits=2):
debit_rounded = float_round(l.debit, precision_digits=2)
if float_compare(l.debit, debit_rounded, precision_digits=6):
logger.info('Bad move to fix ID %d company_id %d name %s ref %s date %s journal %s (line ID %d debit=%s)', move.id, move.company_id.id, move.name, move.ref, move.date, move.journal_id.code, l.id, l.debit)
buggy = True
break
else:
credit_rounded = float_round(l.credit, precision_digits=2)
if float_compare(l.credit, credit_rounded, precision_digits=6):
logger.info('Bad move to fix ID %d company_id %d name %s ref %s date %s journal %s (line ID %d credit=%s)', move.id, move.company_id.id, move.name, move.ref, move.date, move.journal_id.code, l.id, l.credit)
buggy = True
break
if buggy:
bug_move_ids.append(move.id)
bal = 0.0
max_credit = (False, 0)
for l in move.line_ids:
if not float_is_zero(l.debit, precision_digits=2):
new_debit = float_round(l.debit, precision_digits=2)
self._cr.execute(
'UPDATE account_move_line set debit=%s, balance=%s where id=%s',
(new_debit, new_debit, l.id))
bal -= new_debit
elif not float_is_zero(l.credit, precision_digits=2):
new_credit = float_round(l.credit, precision_digits=2)
self._cr.execute(
'UPDATE account_move_line set credit=%s, balance=%s where id=%s',
(new_credit, new_credit * -1, l.id))
bal += new_credit
if new_credit > max_credit[1]:
max_credit = (l, new_credit)
if not float_is_zero(bal, precision_digits=2):
assert abs(bal) < 0.05
l = max_credit[0]
new_credit = max_credit[1]
new_new_credit = float_round(new_credit - bal, precision_digits=2)
assert new_new_credit > 0
self._cr.execute(
'UPDATE account_move_line set credit=%s, balance=%s where id=%s',
(new_new_credit, new_new_credit * -1, l.id))
logger.info('Move ID %d fixed', move.id)
logger.info('%d buggy moves fixed (IDs: %s)', len(bug_move_ids), bug_move_ids)
logger.info('END detect_equilibre_bug')
class AccountMoveLine(models.Model):
@@ -278,6 +438,14 @@ class AccountMoveLine(models.Model):
# Update field only to add a string (there is no string in account module)
invoice_id = fields.Many2one(string='Invoice')
date_maturity = fields.Date(copy=False)
account_reconcile = fields.Boolean(
related='account_id.reconcile', readonly=True)
full_reconcile_id = fields.Many2one(string='Full Reconcile')
matched_debit_ids = fields.One2many(string='Partial Reconcile Debit')
matched_credit_ids = fields.One2many(string='Partial Reconcile Credit')
reconcile_string = fields.Char(
compute='_compute_reconcile_string', string='Reconcile', store=True)
@api.onchange('credit')
def _credit_onchange(self):
@@ -300,9 +468,7 @@ class AccountMoveLine(models.Model):
prec = self.env['decimal.precision'].precision_get('Account')
if (
self.currency_id and
self.amount_currency and
float_is_zero(self.credit, precision_digits=prec) and
float_is_zero(self.debit, precision_digits=prec)):
not float_is_zero(self.amount_currency, precision_digits=prec)):
date = self.date or None
amount_company_currency = self.currency_id.with_context(
date=date).compute(
@@ -311,11 +477,10 @@ class AccountMoveLine(models.Model):
if float_compare(
amount_company_currency, 0,
precision_digits=precision) == -1:
self.debit = amount_company_currency * -1
self.credit = amount_company_currency * -1
else:
self.credit = amount_company_currency
self.debit = amount_company_currency
@api.multi
def show_account_move_form(self):
self.ensure_one()
action = self.env['ir.actions.act_window'].for_xml_id(
@@ -328,6 +493,34 @@ class AccountMoveLine(models.Model):
})
return action
@api.depends(
'full_reconcile_id', 'matched_debit_ids', 'matched_credit_ids')
def _compute_reconcile_string(self):
for line in self:
rec_str = False
if line.full_reconcile_id:
rec_str = line.full_reconcile_id.name
else:
rec_str = ', '.join([
'a%d' % pr.id for pr in line.matched_debit_ids + line.matched_credit_ids])
line.reconcile_string = rec_str
class AccountPartialReconcile(models.Model):
_inherit = "account.partial.reconcile"
_rec_name = "id"
def name_get(self):
res = []
for rec in self:
# There is no seq for partial rec, so I simulate one with the ID
# Prefix for full rec: 'A' (upper case)
# Prefix for partial rec: 'a' (lower case)
amount_fmt = formatLang(self.env, rec.amount, currency_obj=rec.company_currency_id)
name = 'a%d (%s)' % (rec.id, amount_fmt)
res.append((rec.id, name))
return res
class AccountBankStatement(models.Model):
_inherit = 'account.bank.statement'
@@ -338,6 +531,8 @@ class AccountBankStatement(models.Model):
end_date = fields.Date(
compute='_compute_dates', string='End Date', readonly=True,
store=True)
hide_bank_statement_balance = fields.Boolean(
related='journal_id.hide_bank_statement_balance', readonly=True)
@api.multi
@api.depends('line_ids.date')
@@ -347,6 +542,16 @@ class AccountBankStatement(models.Model):
st.start_date = dates and min(dates) or False
st.end_date = dates and max(dates) or False
@api.multi
@api.depends('name', 'start_date', 'end_date')
def name_get(self):
res = []
for statement in self:
name = "%s (%s => %s)" % (
statement.name, statement.start_date, statement.end_date)
res.append((statement.id, name))
return res
class AccountBankStatementLine(models.Model):
_inherit = 'account.bank.statement.line'
@@ -398,6 +603,15 @@ class AccountBankStatementLine(models.Model):
vals['ref'] = False
return vals
def get_statement_line_for_reconciliation_widget(self):
# In the work interface of the bank statement, when a partner_id
# is selected, Odoo displays its 'name' => we prefer that it
# displays its 'display_name'.
data = super(AccountBankStatementLine, self).get_statement_line_for_reconciliation_widget()
if self.partner_id:
data['partner_name'] = self.partner_id.display_name
return data
@api.multi
def show_account_move(self):
self.ensure_one()

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Copyright 2018 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 AccountInvoiceReport(models.Model):
_inherit = 'account.invoice.report'
number = fields.Char(string="Number", readonly=True)
def _sub_select(self):
select_str = super(AccountInvoiceReport, self)._sub_select()
select_str += ", ai.number"
return select_str
def _select(self):
select_str = super(AccountInvoiceReport, self)._select()
select_str += ", sub.number"
return select_str
def _group_by(self):
group_by_str = super(AccountInvoiceReport, self)._group_by()
group_by_str += ", ai.number"
return group_by_str

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 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="account_invoice_report_tree" model="ir.ui.view">
<field name="name">usability.account.invoice.report.tree</field>
<field name="model">account.invoice.report</field>
<field name="arch" type="xml">
<tree string="Invoices Analysis">
<field name="number"/>
<field name="date"/>
<field name="date_due"/>
<field name="type"/>
<field name="commercial_partner_id"/>
<field name="user_id"/>
<field name="product_id"/>
<field name="product_qty" sum="1"/>
<field name="uom_name" groups="product.group_uom"/>
<field name="price_total" sum="1"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="account.action_account_invoice_report_all_supp" model="ir.actions.act_window">
<field name="context">{'search_default_current': 1, 'search_default_supplier': 1, 'search_default_year': 1}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
</record>
<record id="account.action_account_invoice_report_all" model="ir.actions.act_window">
<field name="context">{'search_default_current': 1, 'search_default_customer': 1, 'search_default_year': 1}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
</record>
<record id="view_account_invoice_report_pivot" model="ir.ui.view">
<field name="name">usability.account.invoice.report</field>
<field name="model">account.invoice.report</field>
<field name="inherit_id" ref="account.view_account_invoice_report_pivot"/>
<field name="arch" type="xml">
<pivot position="attributes">
<attribute name="disable_linking"></attribute>
</pivot>
</field>
</record>
</odoo>

View File

@@ -1,8 +1,8 @@
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 8ed1e48..615da43 100644
index dc3247154be..077e004b53c 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -563,7 +563,13 @@ class AccountBankStatementLine(models.Model):
@@ -566,7 +566,13 @@ class AccountBankStatementLine(models.Model):
"""
# Blue lines = payment on bank account not assigned to a statement yet
reconciliation_aml_accounts = [self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id]
@@ -18,10 +18,10 @@ index 8ed1e48..615da43 100644
# Black lines = unreconciled & (not linked to a payment or open balance created by statement
domain_matching = [('reconciled', '=', False)]
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index b60ffbe..6c27c57 100644
index 6a0fed7d143..ecc2ed67936 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -599,6 +599,7 @@ class AccountMoveLine(models.Model):
@@ -633,6 +633,7 @@ class AccountMoveLine(models.Model):
domain = expression.AND([domain, [('id', 'not in', excluded_ids)]])
if str:
str_domain = [
@@ -30,7 +30,7 @@ index b60ffbe..6c27c57 100644
'|', ('move_id.ref', 'ilike', str),
'|', ('date_maturity', 'like', str),
diff --git a/addons/account/static/src/js/account_reconciliation_widgets.js b/addons/account/static/src/js/account_reconciliation_widgets.js
index 453bd41..48c396e 100644
index 5d00984157c..836fe37fc2f 100644
--- a/addons/account/static/src/js/account_reconciliation_widgets.js
+++ b/addons/account/static/src/js/account_reconciliation_widgets.js
@@ -76,7 +76,7 @@ var abstractReconciliation = Widget.extend(ControlPanelMixin, {
@@ -42,7 +42,7 @@ index 453bd41..48c396e 100644
// Number of reconciliations loaded initially and by clicking 'show more'
this.num_reconciliations_fetched_in_batch = 10;
this.animation_speed = 100; // "Blocking" animations
@@ -1755,7 +1755,7 @@ var bankStatementReconciliationLine = abstractReconciliationLine.extend({
@@ -1757,7 +1757,7 @@ var bankStatementReconciliationLine = abstractReconciliationLine.extend({
relation: "res.partner",
string: _t("Partner"),
type: "many2one",

View File

@@ -19,6 +19,9 @@
<field name="invoice_line_ids" position="before">
<button name="delete_lines_qty_zero" states="draft" string="⇒ Delete lines qty=0" type="object" class="oe_link oe_right" groups="account.group_account_invoice"/>
</field>
<xpath expr="//field[@name='tax_line_ids']/tree/field[@name='amount']" position="before">
<field name="base" readonly="1"/>
</xpath>
</field>
</record>
@@ -35,6 +38,20 @@
<field name="move_id" position="before">
<field name="sent"/>
</field>
<xpath expr="//field[@name='tax_line_ids']/tree/field[@name='amount']" position="before">
<field name="base" readonly="1"/>
</xpath>
<!-- Don't allow to force tax amount on CUSTOMER invoices -->
<xpath expr="//field[@name='tax_line_ids']/tree/field[@name='amount']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
<!-- Warning: there are 2 invoice_print buttons in the native view... probably a bug -->
<xpath expr="//button[@name='invoice_print']" position="attributes">
<attribute name="attrs">{'invisible': [('state', 'not in', ('open', 'paid'))]}</attribute>
</xpath>
<xpath expr="//button[@name='invoice_print'][2]" position="attributes">
<attribute name="attrs">{'invisible': True}</attribute>
</xpath>
</field>
</record>
@@ -135,7 +152,7 @@ module -->
<record id="out_invoice_line_action" model="ir.actions.act_window">
<field name="name">Customer Invoice Lines</field>
<field name="res_model">account.invoice.line</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree</field> <!-- no access to form view until we have a proper readonly system for non draft invoices -->
<field name="domain">[('invoice_type', '=', 'out_invoice')]</field>
<field name="context">{'show_invoice_fields': True}</field>
</record>
@@ -143,7 +160,7 @@ module -->
<record id="out_refund_line_action" model="ir.actions.act_window">
<field name="name">Customer Refund Lines</field>
<field name="res_model">account.invoice.line</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree</field> <!-- no access to form view until we have a proper readonly system for non draft invoices -->
<field name="domain">[('invoice_type', '=', 'out_refund')]</field>
<field name="context">{'show_invoice_fields': True}</field>
</record>
@@ -151,7 +168,7 @@ module -->
<record id="out_invoice_refund_line_action" model="ir.actions.act_window">
<field name="name">Customer Invoice Lines</field>
<field name="res_model">account.invoice.line</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree</field> <!-- no access to form view until we have a proper readonly system for non draft invoices -->
<field name="domain">[('invoice_type', 'in', ('out_invoice', 'out_refund'))]</field>
<field name="context">{'show_invoice_fields': True}</field>
</record>
@@ -159,7 +176,7 @@ module -->
<record id="in_invoice_line_action" model="ir.actions.act_window">
<field name="name">Supplier Invoice Lines</field>
<field name="res_model">account.invoice.line</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree</field> <!-- no access to form view until we have a proper readonly system for non draft invoices -->
<field name="domain">[('invoice_type', '=', 'in_invoice')]</field>
<field name="context">{'show_invoice_fields': True}</field>
</record>
@@ -167,7 +184,7 @@ module -->
<record id="in_refund_line_action" model="ir.actions.act_window">
<field name="name">Supplier Refund Lines</field>
<field name="res_model">account.invoice.line</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree</field> <!-- no access to form view until we have a proper readonly system for non draft invoices -->
<field name="domain">[('invoice_type', '=', 'in_refund')]</field>
<field name="context">{'show_invoice_fields': True}</field>
</record>
@@ -175,7 +192,7 @@ module -->
<record id="in_invoice_refund_line_action" model="ir.actions.act_window">
<field name="name">Supplier Invoice Lines</field>
<field name="res_model">account.invoice.line</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree</field> <!-- no access to form view until we have a proper readonly system for non draft invoices -->
<field name="domain">[('invoice_type', 'in', ('in_invoice', 'in_refund'))]</field>
<field name="context">{'show_invoice_fields': True}</field>
</record>
@@ -188,6 +205,9 @@ module -->
<field name="categ_id" position="after">
<field name="product_id"/>
</field>
<filter name="thisyear" position="after">
<filter name="this_year_and_previous" string="This year and previous" domain="['|', ('date', '=', False), '&amp;',('date','&lt;=', (context_today() + relativedelta(day=31, month=12)).strftime('%Y-%m-%d')), ('date', '&gt;=', (context_today() + relativedelta(day=1, month=1, years=-1)).strftime('%Y-%m-%d'))]"/>
</filter>
</field>
</record>
@@ -241,6 +261,17 @@ module -->
</field>
</record>
<record id="view_account_payment_form" model="ir.ui.view">
<field name="name">usability.account.payment.form</field>
<field name="model">account.payment</field>
<field name="inherit_id" ref="account.view_account_payment_form"/>
<field name="arch" type="xml">
<field name="communication" position="after">
<field name="payment_reference"/>
</field>
</field>
</record>
<!-- model account.move.line / Journal Items -->
<record id="account.action_account_moves_all_a" model="ir.actions.act_window">
<field name="limit">200</field>
@@ -271,12 +302,18 @@ module -->
<field name="arch" type="xml">
<field name="ref" position="after">
<field name="default_move_line_name"/>
<field name="default_account_id" invisible="1"/>
<field name="default_credit" invisible="1"/>
<field name="default_debit" invisible="1"/>
</field>
<xpath expr="//field[@name='line_ids']" position="attributes">
<attribute name="context">{'line_ids': line_ids, 'journal_id': journal_id, 'default_name': default_move_line_name}</attribute>
<attribute name="context" operation="python_dict" key="default_name">default_move_line_name</attribute>
<attribute name="context" operation="python_dict" key="default_account_id">default_account_id</attribute>
<attribute name="context" operation="python_dict" key="default_credit">default_credit</attribute>
<attribute name="context" operation="python_dict" key="default_debit">default_debit</attribute>
</xpath>
<xpath expr="//field[@name='line_ids']/tree/field[@name='date_maturity']" position="after">
<field name="full_reconcile_id"/>
<xpath expr="//field[@name='line_ids']/tree/field[@name='credit']" position="after">
<field name="reconcile_string"/>
</xpath>
</field>
</record>
@@ -286,8 +323,14 @@ module -->
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_account_move_line_filter"/>
<field name="arch" type="xml">
<filter domain="[('move_id.state','=','draft')]" position="before">
<filter name="current_year" string="Current Year" domain="[('date', '&gt;=', (context_today().strftime('%Y-01-01'))), ('date', '&lt;=', (context_today().strftime('%Y-12-31')))]"/>
<filter name="previous_year" string="Previous Year" domain="[('date', '&gt;=', (context_today() + relativedelta(day=1, month=1, years=-1)).strftime('%Y-%m-%d')), ('date', '&lt;=', (context_today() + relativedelta(day=31, month=12, years=-1)).strftime('%Y-%m-%d'))]"/>
<separator/>
</filter>
<field name="partner_id" position="after">
<field name="full_reconcile_id" />
<field name="reconcile_string" />
<field name="debit" filter_domain="['|', ('debit', '=', self), ('credit', '=', self)]" string="Debit or Credit"/>
</field>
<filter name="unreconciled" position="before">
<filter name="reconciled" string="Fully Reconciled" domain="[('full_reconcile_id', '!=', False)]"/>
@@ -299,6 +342,9 @@ module -->
<field name="name" position="attributes">
<attribute name="string">Name or Reference</attribute>
</field>
<field name="partner_id" position="attributes">
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
</field>
</field>
</record>
@@ -312,7 +358,21 @@ module -->
</field>
<field name="move_id" position="after">
<field name="invoice_id"/>
<field name="account_reconcile" invisible="1"/>
</field>
<xpath expr="//field[@name='full_reconcile_id']/.." position="replace">
<field name="full_reconcile_id" nolabel="1"/> <!-- label is already in view -->
<field name="matched_debit_ids" readonly="1" widget="many2many_tags" attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '=', [])]}"/>
<field name="matched_credit_ids" readonly="1" widget="many2many_tags" attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), ('matched_credit_ids', '=', [])]}"/>
<field name="reconciled" invisible="1"/>
<button name="open_reconcile_view" class="oe_link" type="object"
string="-> View partially reconciled entries" colspan="2"
attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), '&amp;', ('matched_debit_ids', '=', []),('matched_credit_ids', '=', [])]}"/>
<span colspan="2" attrs="{'invisible': ['|', '|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '!=', []), ('matched_credit_ids', '!=', [])]}" class="o_form_field">No Partial Reconcile</span>
</xpath>
<xpath expr="//label[@for='full_reconcile_id']/.." position="attributes">
<attribute name="attrs">{'invisible': [('account_reconcile', '=', False)]}</attribute>
</xpath>
</field>
</record>
@@ -321,10 +381,12 @@ module -->
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree"/>
<field name="arch" type="xml">
<!-- Move reconcile_id to a better position -->
<field name="full_reconcile_id" position="replace"/>
<field name="full_reconcile_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="credit" position="after">
<field name="full_reconcile_id"/>
<field name="balance" sum="Total Balance"/>
<field name="reconcile_string"/>
</field>
<field name="date_maturity" position="after">
<button name="show_account_move_form" type="object" icon="fa-arrows-h" string="Show Journal Entry"/>
@@ -332,6 +394,33 @@ module -->
</field>
</record>
<!-- By default, the pivot view displays the journal as row
which is really not interesting from an accountant point of view
So I prefer to display account_id by default on row.
The only drawback is that it makes quite a big pivot table
by default -->
<record id="view_move_line_pivot" model="ir.ui.view">
<field name="name">usability.account.move.line.pivot</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_pivot"/>
<field name="arch" type="xml">
<field name="journal_id" position="replace">
<field name="account_id" type="row"/>
</field>
</field>
</record>
<record id="view_account_move_filter" model="ir.ui.view">
<field name="name">account_usability.account_move_search</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_move_filter"/>
<field name="arch" type="xml">
<field name="partner_id" position="attributes">
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
</field>
</field>
</record>
<record id="view_account_search" model="ir.ui.view">
<field name="name">account.account.search</field>
<field name="model">account.account</field>
@@ -357,6 +446,42 @@ module -->
</field>
</record>
<record id="view_account_journal_form" model="ir.ui.view">
<field name="name">usability.account.journal.form</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
<field name="bank_statements_source" position="after">
<field name="hide_bank_statement_balance"/>
</field>
</field>
</record>
<record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
<field name="name">usability.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">
<field name="show_on_dashboard" position="after">
<field name="hide_bank_statement_balance"/>
</field>
<xpath expr="//div[@name='latest_statement']/.." position="attributes">
<attribute name="t-if">dashboard.last_balance != dashboard.account_balance &amp;&amp; !record.hide_bank_statement_balance.raw_value</attribute>
</xpath>
</field>
</record>
<record id="view_account_journal_tree" model="ir.ui.view">
<field name="name">usability.account.journal.tree</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_tree"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="code"/>
</field>
</field>
</record>
<record id="view_account_journal_search" model="ir.ui.view">
<field name="name">usability.account.journal.search</field>
<field name="model">account.journal</field>
@@ -375,6 +500,9 @@ module -->
<field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_form"/>
<field name="arch" type="xml">
<button name="button_cancel" position="attributes">
<attribute name="invisible">1</attribute>
</button>
<xpath expr="//field[@name='line_ids']/tree/field[@name='bank_account_id']" position="after">
<!-- The cancel button is provided by the account_cancel module, but we don't want to depend on it -->
<button name="show_account_move" type="object"
@@ -384,10 +512,26 @@ module -->
<field name="date" position="after">
<field name="start_date"/>
<field name="end_date"/>
<field name="hide_bank_statement_balance" invisible="1"/>
</field>
<field name="date" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<label for="balance_start" position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</label>
<label for="balance_end_real" position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</label>
<xpath expr="//field[@name='balance_start']/.." position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</xpath>
<xpath expr="//field[@name='balance_end_real']/.." position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</xpath>
<group name="sale_total" position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</group>
</field>
</record>
@@ -471,6 +615,16 @@ because it is useless and confusing -->
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
</record>
<!-- Remove menu entry "Accounting > Reports > PDF Reports" as there are broken -->
<record id="account.menu_finance_legal_statement" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
</record>
<!-- Also hide the corresponding configuration menu "Accounting > Configuration > Financial Reports" -->
<record id="account.menu_account_reports" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
</record>
<!-- Duplicate the menu "Sales > Configuration > Contacts > Bank Accounts"
under "Accounting > Configuration", because most users will try to find it there -->
<menuitem id="bank_account_account_config_menu" name="Bank Accounts" parent="account.menu_finance_configuration" sequence="9"/>

View File

@@ -0,0 +1,561 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_usability
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 16:16+0000\n"
"PO-Revision-Date: 2020-10-23 16:16+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_usability
#: model:ir.model.fields,help:account_usability.field_account_invoice_line_state
msgid " * The 'Draft' status is used when a user is encoding a new and unconfirmed Invoice.\n"
" * The 'Pro-forma' status is used when the invoice does not have an invoice number.\n"
" * The 'Open' status is used when user creates invoice, an invoice number is generated. It stays in the open status till the user pays the invoice.\n"
" * The 'Paid' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled.\n"
" * The 'Cancelled' status is used when user cancel invoice."
msgstr "* L'état \"Brouillon\" est utilisé lorsqu'un utilisateur est en train de saisir ou de modifier une nouvelle facture non confirmée.\n"
"* L'état \"Pro-forma\" est utilisé lorsque la facture n'a pas de numéro de facture.\n"
"* L'état 'Ouvert' est utilisé lorsque l'utilisateur crée une facture, celle-ci a alors un numéro de facture. La facture reste dans l'état \"Ouvert\" tant qu'elle n'est pas payée.\n"
"* L'état 'Payé' est affecté automatiquement lorsque la facture est payée. Les écritures correspondantes dans les journaux peuvent ou non être lettrées.\n"
"* L'état \"Annulé\" est utilisé lorsque l'utilisateur annule la facture."
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_move_line_form
msgid "-> View partially reconciled entries"
msgstr "-> Voir les écritures partiellement lettrées"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_move_line_form
msgid "<span colspan=\"2\" attrs=\"{'invisible': ['|', '|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '!=', []), ('matched_credit_ids', '!=', [])]}\" class=\"o_form_field\">No Partial Reconcile</span>"
msgstr "<span colspan=\"2\" attrs=\"{'invisible': ['|', '|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '!=', []), ('matched_credit_ids', '!=', [])]}\" class=\"o_form_field\">No Partial Reconcile</span>"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_account
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Account"
msgstr "Compte"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_move
msgid "Account Entry"
msgstr "Pièce comptable"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_move_backtodraft
msgid "Account Move Unpost"
msgstr "Account Move Unpost"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_move_reversal
msgid "Account move reversal"
msgstr "Extourne de la pièce comptable"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_move_backtodraft_form
msgid "All selected journal entries will be unposted (if allowed by the journal configuration)."
msgstr "All selected journal entries will be unposted (if allowed by the journal configuration)."
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move_line_account_reconcile
msgid "Allow Reconciliation"
msgstr "Autoriser le lettrage"
#. module: account_usability
#: sql_constraint:account.analytic.account:0
msgid "An analytic account with the same code already exists in the same company!"
msgstr "Un compte analytique avec le même code existe déjà pour la même société !"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_analytic_account
msgid "Analytic Account"
msgstr "Compte analytique"
#. module: account_usability
#: model:ir.ui.menu,name:account_usability.bank_account_account_config_menu
#: model:ir.ui.menu,name:account_usability.res_partner_bank_account_config_menu
msgid "Bank Accounts"
msgstr "Comptes bancaires"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_bank_statement
msgid "Bank Statement"
msgstr "Relevé bancaire"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_bank_statement_line
msgid "Bank Statement Line"
msgstr "Ligne de relevé bancaire"
#. module: account_usability
#: model:ir.ui.menu,name:account_usability.res_bank_account_config_menu
msgid "Banks"
msgstr "Banques"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_mark_sent_form
#: model:ir.ui.view,arch_db:account_usability.account_move_backtodraft_form
msgid "Cancel"
msgstr "Annuler"
#. module: account_usability
#: model:ir.model.fields,help:account_usability.field_account_move_line_account_reconcile
msgid "Check this box if this account allows invoices & payments matching of journal items."
msgstr "Cochez cette case si ce compte permet de faire du rapprochement entre factures et paiements."
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_search
msgid "Code"
msgstr "Code"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_line_commercial_partner_id
msgid "Commercial Entity"
msgstr "Entité commerciale"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_mark_sent_create_uid
#: model:ir.model.fields,field_description:account_usability.field_account_move_backtodraft_create_uid
msgid "Created by"
msgstr "Créé par"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_mark_sent_create_date
#: model:ir.model.fields,field_description:account_usability.field_account_move_backtodraft_create_date
msgid "Created on"
msgstr "Créé le"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_move_line_filter
msgid "Current Year"
msgstr "Année en cours"
#. module: account_usability
#: model:ir.actions.act_window,name:account_usability.out_invoice_line_action
#: model:ir.actions.act_window,name:account_usability.out_invoice_refund_line_action
msgid "Customer Invoice Lines"
msgstr "Lignes de facture client"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Customer Invoices"
msgstr "Factures clients"
#. module: account_usability
#: model:ir.actions.act_window,name:account_usability.out_refund_line_action
msgid "Customer Refund Lines"
msgstr "Lignes d'avoir client"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Customer Refunds"
msgstr "Avoirs client"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_move_line_filter
msgid "Debit or Credit"
msgstr "Débit ou crédit"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move_default_account_id
msgid "Default Debit Account"
msgstr "Compte de débit par défaut"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move_default_move_line_name
msgid "Default Label"
msgstr "Libellé par défaut"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move_default_credit
msgid "Default credit"
msgstr "Default credit"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move_default_debit
msgid "Default debit"
msgstr "Default debit"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_mark_sent_display_name
#: model:ir.model.fields,field_description:account_usability.field_account_move_backtodraft_display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Draft"
msgstr "Brouillon"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_bank_statement_end_date
#: model:ir.ui.view,arch_db:account_usability.view_bank_statement_search
msgid "End Date"
msgstr "Date de fin"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_fiscal_position
msgid "Fiscal Position"
msgstr "Position fiscale"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_move_line_filter
msgid "Fully Reconciled"
msgstr "Lettré totalement"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
#: model:ir.ui.view,arch_db:account_usability.view_account_journal_search
msgid "Group By"
msgstr "Regrouper par"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_has_attachment
msgid "Has attachment"
msgstr "Pièce(s) jointe(s) présente(s)"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_has_discount
msgid "Has discount"
msgstr "A une remise"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_bank_statement_hide_bank_statement_balance
#: model:ir.model.fields,field_description:account_usability.field_account_journal_hide_bank_statement_balance
msgid "Hide Bank Statement Balance"
msgstr "Masquer le solde du relevé"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_mark_sent_id
#: model:ir.model.fields,field_description:account_usability.field_account_move_backtodraft_id
msgid "ID"
msgstr "ID"
#. module: account_usability
#: model:ir.model,name:account_usability.model_product_supplierinfo
msgid "Information about a product vendor"
msgstr "Information sur le vendeur de l'article"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_invoice
msgid "Invoice"
msgstr "Facture"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_line_date_invoice
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Invoice Date"
msgstr "Date de facturation"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_invoice_line
msgid "Invoice Line"
msgstr "Ligne de facture"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_line_invoice_number
msgid "Invoice Number"
msgstr "Numéro de facture"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_line_state
msgid "Invoice State"
msgstr "État de la facture"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_report_tree
msgid "Invoices Analysis"
msgstr "Analyse des factures"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_invoice_report
msgid "Invoices Statistics"
msgstr "Statistiques des factures"
#. module: account_usability
#: model:ir.model.fields,help:account_usability.field_account_move_default_account_id
msgid "It acts as a default account for debit amount"
msgstr "Ça sert de compte par défaut pour les montants en débit"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_journal
msgid "Journal"
msgstr "Journal"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_move_line
msgid "Journal Item"
msgstr "Écriture comptable"
#. module: account_usability
#: model:ir.model.fields,help:account_usability.field_account_invoice_line_date_invoice
msgid "Keep empty to use the current date"
msgstr "Laissez vide pour utiliser la date courante"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_mark_sent___last_update
#: model:ir.model.fields,field_description:account_usability.field_account_move_backtodraft___last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_mark_sent_write_uid
#: model:ir.model.fields,field_description:account_usability.field_account_move_backtodraft_write_uid
msgid "Last Updated by"
msgstr "Mis à jour par"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_mark_sent_write_date
#: model:ir.model.fields,field_description:account_usability.field_account_move_backtodraft_write_date
msgid "Last Updated on"
msgstr "Mis à jour le"
#. module: account_usability
#: model:ir.actions.act_window,name:account_usability.account_invoice_mark_sent_action
#: model:ir.ui.view,arch_db:account_usability.account_invoice_mark_sent_form
msgid "Mark as Sent"
msgstr "Marquer comme envoyé"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_invoice_mark_sent
#: model:ir.ui.view,arch_db:account_usability.account_invoice_mark_sent_form
msgid "Mark invoices as sent"
msgstr "Marquer les factures comme envoyées"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_invoice_filter
msgid "Missing Attachment"
msgstr "Missing Attachment"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_move_line_filter
msgid "Name or Reference"
msgstr "Name or Reference"
#. module: account_usability
#: code:addons/account_usability/account.py:615
#, python-format
msgid "No journal entry linked to this bank statement line."
msgstr "No journal entry linked to this bank statement line."
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Not Paid"
msgstr "Non payées"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_report_number
msgid "Number"
msgstr "Numéro"
#. module: account_usability
#: code:addons/account_usability/account.py:218
#, python-format
msgid "On journal '%s', the default credit account '%s' should be configured with Type = 'Bank and Cash'."
msgstr "On journal '%s', the default credit account '%s' should be configured with Type = 'Bank and Cash'."
#. module: account_usability
#: code:addons/account_usability/account.py:209
#, python-format
msgid "On journal '%s', the default debit account '%s' should be configured with Type = 'Bank and Cash'."
msgstr "On journal '%s', the default debit account '%s' should be configured with Type = 'Bank and Cash'."
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Paid"
msgstr "Payé"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_partial_reconcile
msgid "Partial Reconcile"
msgstr "Lettrage partiel"
#. module: account_usability
#: model:ir.model,name:account_usability.model_res_partner
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Partner"
msgstr "Partenaire"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_reconcile_model
msgid "Preset to create journal entries during a invoices and payments matching"
msgstr "Préconfigurer pour créer une écriture pendant la correspondance entre des factures et des paiements"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_move_line_filter
msgid "Previous Year"
msgstr "Année précédente"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Product"
msgstr "Article"
#. module: account_usability
#: model:ir.model,name:account_usability.model_product_template
msgid "Product Template"
msgstr "Modèle d'article"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_product_product_purchase_price_type
#: model:ir.model.fields,field_description:account_usability.field_product_supplierinfo_purchase_price_type
#: model:ir.model.fields,field_description:account_usability.field_product_template_purchase_price_type
msgid "Purchase Price Type"
msgstr "Type de prix d'achat"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move_line_reconcile_string
msgid "Reconcile"
msgstr "Reconcile"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_product_product_sale_price_type
#: model:ir.model.fields,field_description:account_usability.field_product_template_sale_price_type
msgid "Sale Price Type"
msgstr "Type de prix de vente"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Search Invoice Lines"
msgstr "Search Invoice Lines"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_invoice_filter
msgid "Sent"
msgstr "Envoyé"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_move_line_tree
msgid "Show Journal Entry"
msgstr "Show Journal Entry"
#. module: account_usability
#: code:addons/account_usability/account.py:291
#, python-format
msgid "Some account groups already exists"
msgstr "Some account groups already exists"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_bank_statement_start_date
#: model:ir.ui.view,arch_db:account_usability.view_bank_statement_search
msgid "Start Date"
msgstr "Date de début"
#. module: account_usability
#: model:ir.actions.act_window,name:account_usability.in_invoice_line_action
#: model:ir.actions.act_window,name:account_usability.in_invoice_refund_line_action
msgid "Supplier Invoice Lines"
msgstr "Supplier Invoice Lines"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Supplier Invoices"
msgstr "Supplier Invoices"
#. module: account_usability
#: model:ir.actions.act_window,name:account_usability.in_refund_line_action
msgid "Supplier Refund Lines"
msgstr "Supplier Refund Lines"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_line_search
msgid "Supplier Refunds"
msgstr "Supplier Refunds"
#. module: account_usability
#: model:ir.model,name:account_usability.model_account_tax
#: model:ir.ui.view,arch_db:account_usability.product_supplierinfo_tree_view
msgid "Tax"
msgstr "Taxe"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_tax_group_form
msgid "Tax Group"
msgstr "Tax Group"
#. module: account_usability
#: model:ir.actions.act_window,name:account_usability.account_tax_group_action
#: model:ir.ui.menu,name:account_usability.account_tax_group_menu
#: model:ir.ui.view,arch_db:account_usability.account_tax_group_tree
msgid "Tax Groups"
msgstr "Tax Groups"
#. module: account_usability
#: code:addons/account_usability/product.py:22
#, python-format
msgid "Tax excl."
msgstr "HT"
#. module: account_usability
#: code:addons/account_usability/product.py:22
#, python-format
msgid "Tax incl."
msgstr "TTC"
#. module: account_usability
#: code:addons/account_usability/wizard/account_move_backtodraft.py:20
#, python-format
msgid "There is no journal items in posted state to unpost."
msgstr "There is no journal items in posted state to unpost."
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.account_invoice_mark_sent_form
msgid "This wizard will mark as <i>sent</i> all the selected invoices in open or paid state."
msgstr "This wizard will mark as <i>sent</i> all the selected invoices in open or paid state."
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_invoice_report_search
msgid "This year and previous"
msgstr "Cette année et la précédente"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_invoice_filter
msgid "To Send"
msgstr "A envoyer"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_move_line_tree
msgid "Total Balance"
msgstr "Total Balance"
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_invoice_line_invoice_type
#: model:ir.ui.view,arch_db:account_usability.view_account_journal_search
msgid "Type"
msgstr "Type"
#. module: account_usability
#: model:ir.actions.act_window,name:account_usability.account_move_backtodraft_action
#: model:ir.ui.view,arch_db:account_usability.account_move_backtodraft_form
msgid "Unpost Journal Entries"
msgstr "Unpost Journal Entries"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_account_move_line_filter
msgid "Unreconciled or Partially Reconciled"
msgstr "Non lettré ou partiellement lettré"
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.view_bank_statement_form
msgid "View Account Move"
msgstr "View Account Move"
#. module: account_usability
#: model:ir.model.fields,help:account_usability.field_account_bank_statement_hide_bank_statement_balance
#: model:ir.model.fields,help:account_usability.field_account_journal_hide_bank_statement_balance
msgid "You may want to enable this option when your bank journal is generated from a bank statement file that doesn't handle start/end balance (QIF for instance) and you don't want to enter the start/end balance manually: it will prevent the display of wrong information in the accounting dashboard and on bank statements."
msgstr "You may want to enable this option when your bank journal is generated from a bank statement file that doesn't handle start/end balance (QIF for instance) and you don't want to enter the start/end balance manually: it will prevent the display of wrong information in the accounting dashboard and on bank statements."
#. module: account_usability
#: model:ir.ui.view,arch_db:account_usability.invoice_supplier_form
msgid "⇒ Delete lines qty=0"
msgstr "⇒ Supprimer les lignes qté=0"

View File

@@ -0,0 +1,33 @@
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index b1d8012329d..b8a8e2a673d 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -210,6 +210,7 @@ class account_payment(models.Model):
payment_difference = fields.Monetary(compute='_compute_payment_difference', readonly=True)
payment_difference_handling = fields.Selection([('open', 'Keep open'), ('reconcile', 'Mark invoice as fully paid')], default='open', string="Payment Difference", copy=False)
writeoff_account_id = fields.Many2one('account.account', string="Difference Account", domain=[('deprecated', '=', False)], copy=False)
+ writeoff_analytic_account_id = fields.Many2one('account.analytic.account', string="Difference Analytic Account", copy=False)
# FIXME: ondelete='restrict' not working (eg. cancel a bank statement reconciliation with a payment)
move_line_ids = fields.One2many('account.move.line', 'payment_id', readonly=True, copy=False, ondelete='restrict')
@@ -431,6 +432,7 @@ class account_payment(models.Model):
amount_currency_wo = -abs(amount_currency_wo)
writeoff_line['name'] = _('Counterpart')
writeoff_line['account_id'] = self.writeoff_account_id.id
+ writeoff_line['analytic_account_id'] = self.writeoff_analytic_account_id.id or False
writeoff_line['debit'] = debit_wo
writeoff_line['credit'] = credit_wo
writeoff_line['amount_currency'] = amount_currency_wo
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 2460458fbaa..4065d8f9952 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -206,6 +206,8 @@
</div>
<field name="writeoff_account_id" string="Post Difference In"
attrs="{'invisible': [('payment_difference_handling','=','open')], 'required': [('payment_difference_handling', '=', 'reconcile')]}"/>
+ <field name="writeoff_analytic_account_id" string="Post Difference In Analytic Account"
+ attrs="{'invisible': [('payment_difference_handling','=','open')]}"/>
</group>
</group>
</sheet>

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# © 2015-2016 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 api, fields, models, _
class ProductTemplate(models.Model):
_inherit = 'product.template'
# DON'T put store=True on those fields, because they are company dependent
sale_price_type = fields.Selection(
'_sale_purchase_price_type_sel', compute='_compute_sale_price_type',
string='Sale Price Type', compute_sudo=False, readonly=True)
purchase_price_type = fields.Selection(
'_sale_purchase_price_type_sel', compute='_compute_purchase_price_type',
string='Purchase Price Type', compute_sudo=False, readonly=True)
@api.model
def _sale_purchase_price_type_sel(self):
return [('incl', _('Tax incl.')), ('excl', _('Tax excl.'))]
@api.depends('taxes_id')
def _compute_sale_price_type(self):
for pt in self:
sale_price_type = 'incl'
if pt.taxes_id and all([not t.price_include for t in pt.taxes_id if t.amount_type == 'percent']):
sale_price_type = 'excl'
pt.sale_price_type = sale_price_type
@api.depends('supplier_taxes_id')
def _compute_purchase_price_type(self):
for pt in self:
purchase_price_type = 'incl'
if pt.supplier_taxes_id and all([not t.price_include for t in pt.supplier_taxes_id if t.amount_type == 'percent']):
purchase_price_type = 'excl'
pt.purchase_price_type = purchase_price_type
class ProductSupplierinfo(models.Model):
_inherit = 'product.supplierinfo'
# DON'T put store=True on those fields, because they are company dependent
purchase_price_type = fields.Selection(
related='product_tmpl_id.purchase_price_type', related_sudo=False)

View File

@@ -1,24 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2017 Akretion (http://www.akretion.com/)
Copyright 2017-2020 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<!-- In the official account module, on product category and product template,
some fields/groups are on account.group_account_invoice, some on
account.group_account_user and some on account.group_account_manager
Here, we set all those fields on account.group_account_invoice
-->
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">account_usability.product.template.form</field>
<field name="model">product.template</field>
<field name="priority">100</field> <!-- when you replace a field, it's always better to inherit at the end -->
<field name="inherit_id" ref="account.product_template_form_view"/>
<field name="arch" type="xml">
<field name="property_account_income_id" position="attributes">
<attribute name="groups">account.group_account_invoice</attribute>
</field>
<field name="property_account_expense_id" position="attributes">
<attribute name="groups">account.group_account_invoice</attribute>
</field>
<field name="list_price" position="replace">
<label for="list_price"/>
<div name="list_price">
<field name="list_price" widget='monetary' options="{'currency_field': 'currency_id'}" class="oe_inline"/>
<label for="sale_price_type" string=" "/>
<field name="sale_price_type"/>
</div>
</field>
</field>
</record>
<record id="view_category_property_form" model="ir.ui.view">
<field name="name">account_usability.product.category.form</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="account.view_category_property_form"/>
<field name="arch" type="xml">
<!-- On product form view, the group for Invoicing tab is limited to account.group_account_invoice... but on product category form, it is limited to account.group_account_manager -> we fix this and also use account.group_account_invoice -->
<group name="account_property" position="attributes">
<attribute name="groups">account.group_account_invoice</attribute>
</group>
</field>
</record>
<record id="product_supplierinfo_form_view" model="ir.ui.view">
<field name="name">account_usability.product.supplierinfo.form</field>
<field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_form_view"/>
<field name="arch" type="xml">
<field name="currency_id" position="after">
<field name="purchase_price_type"/>
</field>
</field>
</record>
<record id="product_supplierinfo_tree_view" model="ir.ui.view">
<field name="name">account_usability.product.supplierinfo.tree</field>
<field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_tree_view"/>
<field name="arch" type="xml">
<field name="price" position="after">
<field name="purchase_price_type" string="Tax"/>
</field>
</field>
</record>
</odoo>

View File

@@ -2,3 +2,4 @@
from . import account_invoice_mark_sent
from . import account_move_reversal
from . import account_move_backtodraft

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# 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 models, _
from odoo.exceptions import UserError
class AccountMoveBacktodraft(models.TransientModel):
_name = 'account.move.backtodraft'
_description = 'Account Move Unpost'
def backtodraft(self):
assert self._context.get('active_model') == 'account.move'
amo = self.env['account.move']
moves = amo.browse(self._context.get('active_ids'))
moves_backtodraft = moves.filtered(lambda x: x.state == 'posted')
if not moves_backtodraft:
raise UserError(_(
'There is no journal items in posted state to unpost.'))
moves_backtodraft.button_cancel()
return True

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_move_backtodraft_form" model="ir.ui.view">
<field name="name">Unpost Journal Entries</field>
<field name="model">account.move.backtodraft</field>
<field name="arch" type="xml">
<form string="Unpost Journal Entries">
<label string="All selected journal entries will be unposted (if allowed by the journal configuration)."/>
<footer>
<button string="Unpost Journal Entries" name="backtodraft" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<act_window id="account_move_backtodraft_action"
multi="True"
key2="client_action_multi"
name="Unpost Journal Entries"
res_model="account.move.backtodraft"
src_model="account.move"
groups="account.group_account_user"
view_mode="form"
target="new" />
</odoo>

View File

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

View File

@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
# Copyright 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).
{
'name': 'Base Dynamic List',
'version': '10.0.1.0.0',
'category': 'Tools',
'license': 'AGPL-3',
'summary': 'Dynamic lists',
'description': """
Base Dynamic List
=================
Very often during an Odoo implementation, we need to add selection fields on a native objet, and we don't want to have a hard-coded selection list (fields.Selection), but a selection list that can be changed by users (Many2one field). For that, the developper needs to add a new object (with just a 'name' and 'sequence' field) with a form/tree view. The goal of this module is to speed-up this process by defining a dynamic list object that already has all the required views.
This module provides several ready-to-go objects:
* simple list : fields *name*, *sequence* and *active*
* translatable list : fields *name* with translate=True, *sequence* and *active*
* code list : fields *code* (unique), *name*, *sequence* and *active*
* translatable code list : fields *code* (unique), *name* with translate=True, *sequence* and *active*
These objects are readable by the employee group. The system group has full rights on it.
To use it, you need to do 2 or 3 things :
1) Add an entry in the domain field and the object you selected:
domain = fields.Selection(selection_add=[('risk.type', "Risk Type")])
2) Add the many2one field on your object:
risk_type_id = fields.Many2one(
'dynamic.list', string="Risk Type",
ondelete='restrict', domain=[('domain', '=', 'risk.type')])
3) Optionally, you can add a dedicated action and a menu entry (otherwize, you can use the generic menu entry under *Settings > Technical > Dynamic Lists*:
<record id="dynamic_list_risk_type_action" model="ir.actions.act_window">
<field name="name">Risk Type</field>
<field name="res_model">dynamic.list</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('domain', '=', 'risk.type')]</field>
<field name="context">{'default_domain': 'risk.type'}</field>
</record>
<menuitem id="dynamic_list_risk_type_menu" action="dynamic_list_risk_type_action"
parent="parent_menu_xmlid"/>
Limitation: when you want to have different access rights on these lists depending on the source object, you should prefer to use dedicated objects.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/dynamic_list.xml',
],
'installable': True,
}

View File

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

View File

@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright 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).
from odoo import api, fields, models
class DynamicList(models.Model):
_name = 'dynamic.list'
_description = 'Dynamic List (non translatable)'
_order = 'sequence, id'
name = fields.Char(required=True)
sequence = fields.Integer()
active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True)
_sql_constraint = [(
'domain_name_uniq',
'unique(domain, name)',
'This entry already exists!'
)]
class DynamicListTranslate(models.Model):
_name = 'dynamic.list.translate'
_description = 'Translatable Dynamic List'
_order = 'sequence, id'
name = fields.Char(translate=True, required=True)
sequence = fields.Integer()
active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True)
_sql_constraint = [(
'domain_name_uniq',
'unique(domain, name)',
'This entry already exists!'
)]
class DynamicListCode(models.Model):
_name = 'dynamic.list.code'
_description = 'Dynamic list with code'
_order = 'sequence, id'
code = fields.Char(required=True)
name = fields.Char(translate=True, required=True)
sequence = fields.Integer()
active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True)
_sql_constraint = [(
'domain_code_uniq',
'unique(domain, code)',
'This code already exists!'
)]
@api.depends('code', 'name')
def name_get(self):
res = []
for rec in self:
res.append((rec.id, u'[%s] %s' % (rec.code, rec.name)))
return res
@api.model
def name_search(
self, name='', args=None, operator='ilike', limit=80):
if args is None:
args = []
if name and operator == 'ilike':
recs = self.search(
[('code', '=', name)] + args, limit=limit)
if recs:
return recs.name_get()
return super(DynamicListCode, self).name_search(
name=name, args=args, operator=operator, limit=limit)
class DynamicListCodeTranslate(models.Model):
_name = 'dynamic.list.code.translate'
_description = 'Translatable dynamic list with code'
_order = 'sequence, id'
code = fields.Char(required=True)
name = fields.Char(translate=True, required=True)
sequence = fields.Integer()
active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True)
_sql_constraint = [(
'domain_code_uniq',
'unique(domain, code)',
'This code already exists!'
)]
@api.depends('code', 'name')
def name_get(self):
res = []
for rec in self:
res.append((rec.id, u'[%s] %s' % (rec.code, rec.name)))
return res
@api.model
def name_search(
self, name='', args=None, operator='ilike', limit=80):
if args is None:
args = []
if name and operator == 'ilike':
recs = self.search(
[('code', '=', name)] + args, limit=limit)
if recs:
return recs.name_get()
return super(DynamicListCodeTranslate, self).name_search(
name=name, args=args, operator=operator, limit=limit)

View File

@@ -0,0 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_dynamic_list_read,Read access on dynamic.list to employees,model_dynamic_list,base.group_user,1,0,0,0
access_dynamic_list_full,Full access to dynamic.list to System group,model_dynamic_list,base.group_system,1,1,1,1
access_dynamic_list_translate_read,Read access on dynamic.list.translate to employees,model_dynamic_list_translate,base.group_user,1,0,0,0
access_dynamic_list_translate_full,Full access to dynamic.list.translate to System group,model_dynamic_list_translate,base.group_system,1,1,1,1
access_dynamic_list_code_read,Read access on dynamic.list.code to employees,model_dynamic_list_code,base.group_user,1,0,0,0
access_dynamic_list_code_full,Full access to dynamic.list.code to System group,model_dynamic_list_code,base.group_system,1,1,1,1
access_dynamic_list_code_translate_read,Read access on dynamic.list.code.translate to employees,model_dynamic_list_code_translate,base.group_user,1,0,0,0
access_dynamic_list_code_translate_full,Full access to dynamic.list.code.translate to System group,model_dynamic_list_code_translate,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_dynamic_list_read Read access on dynamic.list to employees model_dynamic_list base.group_user 1 0 0 0
3 access_dynamic_list_full Full access to dynamic.list to System group model_dynamic_list base.group_system 1 1 1 1
4 access_dynamic_list_translate_read Read access on dynamic.list.translate to employees model_dynamic_list_translate base.group_user 1 0 0 0
5 access_dynamic_list_translate_full Full access to dynamic.list.translate to System group model_dynamic_list_translate base.group_system 1 1 1 1
6 access_dynamic_list_code_read Read access on dynamic.list.code to employees model_dynamic_list_code base.group_user 1 0 0 0
7 access_dynamic_list_code_full Full access to dynamic.list.code to System group model_dynamic_list_code base.group_system 1 1 1 1
8 access_dynamic_list_code_translate_read Read access on dynamic.list.code.translate to employees model_dynamic_list_code_translate base.group_user 1 0 0 0
9 access_dynamic_list_code_translate_full Full access to dynamic.list.code.translate to System group model_dynamic_list_code_translate base.group_system 1 1 1 1

View File

@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 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).
-->
<odoo>
<menuitem id="dynamic_list_root_menu" name="Dynamic Lists" parent="base.menu_custom" sequence="100"/>
<record id="dynamic_list_form" model="ir.ui.view">
<field name="model">dynamic.list</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group name="main">
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="dynamic_list_tree" model="ir.ui.view">
<field name="model">dynamic.list</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/>
</tree>
</field>
</record>
<record id="dynamic_list_search" model="ir.ui.view">
<field name="model">dynamic.list</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
</group>
</search>
</field>
</record>
<record id="dynamic_list_action" model="ir.actions.act_window">
<field name="name">Simple List</field>
<field name="res_model">dynamic.list</field>
<field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_main_view': True, 'search_default_domain_groupby': True}</field>
</record>
<menuitem id="dynamic_list_menu" action="dynamic_list_action" parent="dynamic_list_root_menu" sequence="10"/>
<record id="dynamic_list_translate_form" model="ir.ui.view">
<field name="model">dynamic.list.translate</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group name="main">
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="dynamic_list_translate_tree" model="ir.ui.view">
<field name="model">dynamic.list.translate</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/>
</tree>
</field>
</record>
<record id="dynamic_list_translate_search" model="ir.ui.view">
<field name="model">dynamic.list.translate</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
</group>
</search>
</field>
</record>
<record id="dynamic_list_translate_action" model="ir.actions.act_window">
<field name="name">Translatable Simple List</field>
<field name="res_model">dynamic.list.translate</field>
<field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_translate_main_view': True, 'search_default_domain_groupby': True}</field>
</record>
<menuitem id="dynamic_list_translate_menu" action="dynamic_list_translate_action" parent="dynamic_list_root_menu" sequence="20"/>
<record id="dynamic_list_code_form" model="ir.ui.view">
<field name="model">dynamic.list.code</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group name="main">
<field name="code"/>
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="dynamic_list_code_tree" model="ir.ui.view">
<field name="model">dynamic.list.code</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="code"/>
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/>
</tree>
</field>
</record>
<record id="dynamic_list_code_search" model="ir.ui.view">
<field name="model">dynamic.list.code</field>
<field name="arch" type="xml">
<search>
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<field name="code"/>
<group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
</group>
</search>
</field>
</record>
<record id="dynamic_list_code_action" model="ir.actions.act_window">
<field name="name">Code List</field>
<field name="res_model">dynamic.list.code</field>
<field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_code_main_view': True, 'search_default_domain_groupby': True}</field>
</record>
<menuitem id="dynamic_list_code_menu" action="dynamic_list_code_action" parent="dynamic_list_root_menu" sequence="30"/>
<record id="dynamic_list_code_translate_form" model="ir.ui.view">
<field name="model">dynamic.list.code.translate</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group name="main">
<field name="code"/>
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="dynamic_list_code_translate_tree" model="ir.ui.view">
<field name="model">dynamic.list.code.translate</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="code"/>
<field name="name"/>
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/>
</tree>
</field>
</record>
<record id="dynamic_list_code_translate_search" model="ir.ui.view">
<field name="model">dynamic.list.code.translate</field>
<field name="arch" type="xml">
<search>
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/>
<field name="code"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
</group>
</search>
</field>
</record>
<record id="dynamic_list_code_translate_action" model="ir.actions.act_window">
<field name="name">Translatable Code List</field>
<field name="res_model">dynamic.list.code.translate</field>
<field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_code_translate_main_view': True, 'search_default_domain_groupby': True}</field>
</record>
<menuitem id="dynamic_list_code_translate_menu" action="dynamic_list_code_translate_action" parent="dynamic_list_root_menu" sequence="40"/>
</odoo>

View File

@@ -1,24 +1,3 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Base Partner One2many Phone module for OpenERP
# Copyright (C) 2014 Artisanat Monastique de Provence
# (http://www.barroux.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import partner_phone
from .post_install import migrate_to_partner_phone

View File

@@ -7,21 +7,21 @@
{
'name': 'Base Partner One2many Phone',
'version': '10.0.1.0.0',
'version': '10.0.2.0.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'One2many link between partners and phone numbers',
'summary': 'One2many link between partners and phone numbers/emails',
'description': """
Base Partner One2many Phone
===========================
With this module, one partner can have N phone numbers. It adds a new table dedicated to phone numbers and a one2many link between partners and phone numbers.
With this module, one partner can have several phone numbers and several emails. It adds a new table dedicated to phone numbers and emails and a one2many link between partners and phone numbers. This module keeps compatibility with the native behavior of Odoo on phone numbers and emails.
It has been developped by brother Bernard from Barroux Abbey and Alexis de Lattre from Akretion.
""",
'author': 'Barroux',
'website': 'http://www.barroux.org',
'depends': ['base_phone', 'sales_team'],
'author': 'Akretion',
'website': 'https://akretion.com/',
'depends': ['base_phone', 'sales_team', 'base_usability'],
'data': [
'partner_phone_view.xml',
'security/ir.model.access.csv',

View File

@@ -1,13 +1,13 @@
# Translation of OpenERP Server.
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_partner_one2many_phone
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-02 13:35+0000\n"
"PO-Revision-Date: 2014-07-02 13:35+0000\n"
"POT-Creation-Date: 2020-01-27 18:03+0000\n"
"PO-Revision-Date: 2020-01-27 18:03+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -16,73 +16,157 @@ msgstr ""
"Plural-Forms: \n"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Home"
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_uid
msgid "Created by"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Home Fax"
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_date
msgid "Created on"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Mobile"
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_display_name
msgid "Display Name"
msgstr ""
#. module: base_partner_one2many_phone
#: field:res.partner.phone,note:0
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_email
msgid "E-Mail"
msgstr ""
#. module: base_partner_one2many_phone
#: code:addons/base_partner_one2many_phone/partner_phone.py:61
#, python-format
msgid "E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
msgstr ""
#. module: base_partner_one2many_phone
#: code:addons/base_partner_one2many_phone/partner_phone.py:51
#, python-format
msgid "E-mail field must have a value when type is Primary E-mail or Secondary E-mail."
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_id
msgid "ID"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone___last_update
msgid "Last Modified on"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_uid
msgid "Last Updated by"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_date
msgid "Last Updated on"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_note
msgid "Note"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Office"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Office Fax"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Other"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner
msgid "Partner"
msgstr ""
#. module: base_partner_one2many_phone
#: field:res.partner.phone,phone:0
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_phone
msgid "Phone"
msgstr ""
#. module: base_partner_one2many_phone
#: field:res.partner.phone,type:0
msgid "Phone Type"
#: code:addons/base_partner_one2many_phone/partner_phone.py:54
#, python-format
msgid "Phone field must be empty when type is Primary E-mail or Secondary E-mail."
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Phone/fax Home"
#: code:addons/base_partner_one2many_phone/partner_phone.py:58
#, python-format
msgid "Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_ids
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users_phone_ids
msgid "Phones"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_tree
msgid "Phones and E-mail"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.actions.act_window,name:base_partner_one2many_phone.res_partner_phone_action
#: model:ir.ui.menu,name:base_partner_one2many_phone.res_partner_phone_menu
#: field:res.partner,phone_ids:0
#: view:res.partner.phone:0
msgid "Phones"
msgid "Phones/E-mails"
msgstr ""
#. module: base_partner_one2many_phone
#: field:res.partner.phone,partner_id:0
#: selection:res.partner.phone,type:0
msgid "Primary E-mail"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Primary Fax"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Primary Mobile"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Primary Phone"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_partner_id
msgid "Related Partner"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
msgid "Search Phones/E-mail"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary E-mail"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary Fax"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary Mobile"
msgstr ""
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary Phone"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_type
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
msgid "Type"
msgstr ""
#. module: base_partner_one2many_phone
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner_phone
msgid "res.partner.phone"

View File

@@ -1,89 +1,174 @@
# Translation of OpenERP Server.
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_partner_one2many_phone
# * base_partner_one2many_phone
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-02 13:37+0000\n"
"PO-Revision-Date: 2014-07-02 15:50+0100\n"
"POT-Creation-Date: 2020-01-27 17:56+0000\n"
"PO-Revision-Date: 2020-01-27 17:56+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
"X-Generator: Poedit 1.5.7\n"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Home"
msgstr "Domicile"
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_uid
msgid "Created by"
msgstr "Créé par"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Home Fax"
msgstr "Fax domicile"
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_date
msgid "Created on"
msgstr "Créé le"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Mobile"
msgstr "Mobile"
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_display_name
msgid "Display Name"
msgstr "Nom à afficher"
#. module: base_partner_one2many_phone
#: field:res.partner.phone,note:0
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_email
msgid "E-Mail"
msgstr "Courriel"
#. module: base_partner_one2many_phone
#: code:addons/base_partner_one2many_phone/partner_phone.py:61
#, python-format
msgid "E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
msgstr "Le champ courriel doit être vide quand le type est tél. primaire/secondaire, portable primaire/secondaire ou fax primaire/secondaire."
#. module: base_partner_one2many_phone
#: code:addons/base_partner_one2many_phone/partner_phone.py:51
#, python-format
msgid "E-mail field must have a value when type is Primary E-mail or Secondary E-mail."
msgstr "Le champ courriel doit être renseigné quand le type est courriel primaire ou courriel secondaire."
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_id
msgid "ID"
msgstr "ID"
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone___last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_note
msgid "Note"
msgstr "Note"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Office"
msgstr "Bureau"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Office Fax"
msgstr "Fax bureau"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Other"
msgstr "Autre"
#. module: base_partner_one2many_phone
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner
msgid "Partner"
msgstr "Partenaire"
#. module: base_partner_one2many_phone
#: field:res.partner.phone,phone:0
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_phone
msgid "Phone"
msgstr "Numéro de tél."
msgstr "Téléphone"
#. module: base_partner_one2many_phone
#: field:res.partner.phone,type:0
msgid "Phone Type"
msgstr "Type de téléphone"
#: code:addons/base_partner_one2many_phone/partner_phone.py:54
#, python-format
msgid "Phone field must be empty when type is Primary E-mail or Secondary E-mail."
msgstr "Le champ téléphone doit être vide quand le type est courriel primaire ou courriel secondaire."
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Phone/fax Home"
msgstr "Domicile tél./fax"
#: code:addons/base_partner_one2many_phone/partner_phone.py:58
#, python-format
msgid "Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
msgstr "Le champ téléphone doit être renseigné quand le type est tél. primaire/secondaire, portable primaire/secondaire ou fax primaire/secondaire.."
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_ids
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users_phone_ids
msgid "Phones"
msgstr "Téléphones"
#. module: base_partner_one2many_phone
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_tree
msgid "Phones and E-mail"
msgstr "Téls et courriels"
#. module: base_partner_one2many_phone
#: model:ir.actions.act_window,name:base_partner_one2many_phone.res_partner_phone_action
#: model:ir.ui.menu,name:base_partner_one2many_phone.res_partner_phone_menu
#: field:res.partner,phone_ids:0 view:res.partner.phone:0
msgid "Phones"
msgstr "Numéro de tél."
msgid "Phones/E-mails"
msgstr "Téls/Courriels"
#. module: base_partner_one2many_phone
#: field:res.partner.phone,partner_id:0
#: selection:res.partner.phone,type:0
msgid "Primary E-mail"
msgstr "Courriel principal"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Primary Fax"
msgstr "Fax principal"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Primary Mobile"
msgstr "Portable principal"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Primary Phone"
msgstr "Tél principal"
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_partner_id
msgid "Related Partner"
msgstr "Partenaire lié"
msgstr "Partenaire associé"
#. module: base_partner_one2many_phone
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
msgid "Search Phones/E-mail"
msgstr "Search Phones/E-mail"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary E-mail"
msgstr "Courriel secondaire"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary Fax"
msgstr "Fax secondaire"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary Mobile"
msgstr "Portable secondaire"
#. module: base_partner_one2many_phone
#: selection:res.partner.phone,type:0
msgid "Secondary Phone"
msgstr "Tél. secondaire"
#. module: base_partner_one2many_phone
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_type
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
msgid "Type"
msgstr "Type"
#. module: base_partner_one2many_phone
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner_phone
msgid "res.partner.phone"
msgstr "res.partner.phone"

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# Copyright 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).
from odoo import api, SUPERUSER_ID
oldtype2label = {
'1_home': 'Ancien type : Maison',
# '2_mobile': 'Ancien type : Portable',
'3_office': 'Ancien type : Bureau',
'4_home_fax': 'Ancien type : Fax maison',
'5_office_fax': 'Ancien type : Fax bureau',
'6_phone_fax_home': u'Ancien type : Tél/fax maison',
'7_other': 'Ancien type : Autre',
}
def migrate(cr, version):
if not version:
return
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
rppo = env['res.partner.phone']
# ondelete='cascade' was missing in previous versions
cr.execute('DELETE FROM res_partner_phone WHERE partner_id is null')
wdict = {} # key = partnerID, values = {id: {'type': '1_home', 'phone': '+33'}}
for rec in rppo.search_read([('type', '!=', False)], ['type', 'phone', 'partner_id', 'note']):
if rec['partner_id'][0] not in wdict:
wdict[rec['partner_id'][0]] = {}
wdict[rec['partner_id'][0]][rec['id']] = rec
# first pass for primary phone
for partner_id, xdict in wdict.items():
mig_phone_entries(cr, xdict, '3_phone_primary', '4_phone_secondary', ['1_home', '6_phone_fax_home', '3_office', '7_other'])
mig_phone_entries(cr, xdict, '5_mobile_primary', '6_mobile_secondary', ['2_mobile'])
mig_phone_entries(cr, xdict, '7_fax_primary', '8_fax_secondary', ['4_home_fax', '5_office_fax'])
cr.execute('select id, email from res_partner where email is not null order by id')
for partner in cr.dictfetchall():
print('partner_id=', partner['id'])
old_email = partner['email'].strip()
if old_email:
email_split = old_email.split(',')
clean_email_split = [x.strip() for x in email_split if x.strip()]
# primary:
email_primary = clean_email_split.pop(0)
rppo.create({
'type': '1_email_primary',
'partner_id': partner['id'],
'email': email_primary,
})
cr.execute('UPDATE res_partner set email=%s where id=%s', (email_primary, partner['id']))
for email_sec in clean_email_split:
email_sec = email_sec.strip()
if email_sec:
rppo.create({
'type': '2_email_secondary',
'partner_id': partner['id'],
'email': email_sec.strip(),
})
def mig_phone_entries(cr, xdict, new_type_primary, new_type_secondary, old_type_list):
zdict = {}
for phone_id, values in xdict.items():
if values['type'] in old_type_list:
zdict[phone_id] = values
if zdict:
values_sorted = sorted(zdict.values(), key=lambda x: x['type'])
primary_phone_val = values_sorted[0]
cr.execute("""UPDATE res_partner_phone SET type=%s WHERE id=%s""", (new_type_primary, primary_phone_val['id']))
if not primary_phone_val.get('note') and oldtype2label.get(primary_phone_val['type']):
cr.execute("""UPDATE res_partner_phone SET note=%s WHERE id=%s""", (oldtype2label[primary_phone_val['type']], primary_phone_val['id']))
zdict.pop(primary_phone_val['id'])
for secondary_phone_val in zdict.values():
cr.execute("""UPDATE res_partner_phone SET type=%s WHERE id=%s""", (new_type_secondary, secondary_phone_val['id']))
if not secondary_phone_val.get('note') and oldtype2label.get(secondary_phone_val['type']):
cr.execute("""UPDATE res_partner_phone SET note=%s WHERE id=%s""", (oldtype2label[secondary_phone_val['type']], secondary_phone_val['id']))

View File

@@ -5,9 +5,12 @@
# @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, api
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.addons.base_phone.fields import Phone, Fax
import phonenumbers
EMAIL_TYPES = ('1_email_primary', '2_email_secondary')
PHONE_TYPES = ('3_phone_primary', '4_phone_secondary', '5_mobile_primary', '6_mobile_secondary', '7_fax_primary', '8_fax_secondary')
class ResPartnerPhone(models.Model):
@@ -15,25 +18,55 @@ class ResPartnerPhone(models.Model):
_order = 'partner_id, type'
_phone_name_sequence = 8
partner_id = fields.Many2one('res.partner', string='Related Partner')
partner_id = fields.Many2one(
'res.partner', string='Related Partner', index=True, ondelete='cascade')
type = fields.Selection([
('1_home', 'Home'),
('2_mobile', 'Mobile'),
('3_office', 'Office'),
('4_home_fax', 'Home Fax'),
('5_office_fax', 'Office Fax'),
('6_phone_fax_home', 'Phone/fax Home'),
('7_other', 'Other')],
string='Phone Type', required=True)
phone = Phone('Phone', required=True, partner_field='partner_id')
('1_email_primary', 'Primary E-mail'),
('2_email_secondary', 'Secondary E-mail'),
('3_phone_primary', 'Primary Phone'),
('4_phone_secondary', 'Secondary Phone'),
('5_mobile_primary', 'Primary Mobile'),
('6_mobile_secondary', 'Secondary Mobile'),
('7_fax_primary', 'Primary Fax'),
('8_fax_secondary', 'Secondary Fax'),
],
string='Type', required=True, index=True)
phone = Phone('Phone', required=False, partner_field='partner_id')
email = fields.Char(string='E-Mail')
note = fields.Char('Note')
@api.onchange('type')
def type_change(self):
if self.type:
if self.type in EMAIL_TYPES:
self.phone = False
elif self.type in PHONE_TYPES:
self.email = False
@api.constrains('type', 'phone', 'email')
def _check_partner_phone(self):
for rec in self:
if rec.type in EMAIL_TYPES:
if not rec.email:
raise ValidationError(_(
"E-mail field must have a value when type is Primary E-mail or Secondary E-mail."))
if rec.phone:
raise ValidationError(_(
"Phone field must be empty when type is Primary E-mail or Secondary E-mail."))
elif rec.type in PHONE_TYPES:
if not rec.phone:
raise ValidationError(_(
"Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."))
if rec.email:
raise ValidationError(_(
"E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."))
def name_get(self):
res = []
for pphone in self:
if pphone.partner_id:
if self._context.get('callerid'):
name = pphone.partner_id.name_get()[0][1]
name = pphone.partner_id.display_name
else:
name = u'%s (%s)' % (pphone.phone, pphone.partner_id.name)
else:
@@ -41,51 +74,122 @@ class ResPartnerPhone(models.Model):
res.append((pphone.id, name))
return res
@api.model_cr
def init(self):
self._cr.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS single_email_primary
ON res_partner_phone (partner_id, type)
WHERE (type='1_email_primary')
''')
self._cr.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS single_phone_primary
ON res_partner_phone (partner_id, type)
WHERE (type='3_phone_primary')
''')
self._cr.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS single_mobile_primary
ON res_partner_phone (partner_id, type)
WHERE (type='5_mobile_primary')
''')
self._cr.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS single_fax_primary
ON res_partner_phone (partner_id, type)
WHERE (type='7_fax_primary')
''')
class ResPartner(models.Model):
_inherit = 'res.partner'
@api.model
def convert_from_international_to_e164(self, phone_num):
res = False
try:
res_parse = phonenumbers.parse(phone_num)
res = phonenumbers.format_number(
res_parse, phonenumbers.PhoneNumberFormat.E164)
except:
pass
return res
# without this convert, we would have in DB:
# E.164 format in res_partner_phone table
# phonenumbers.PhoneNumberFormat.INTERNATIONAL in res_partner
# TODO bug: but even with this, it doesn't work, the format
# is stored in international format in res_partner
# => I'll try to find the reason later
@api.multi
@api.depends('phone_ids.phone', 'phone_ids.type')
def _compute_partner_phone(self):
for partner in self:
phone = mobile = fax = False
for partner_phone in partner.phone_ids:
num_e164 = self.convert_from_international_to_e164(
partner_phone.phone)
if num_e164:
if partner_phone.type == '2_mobile':
mobile = num_e164
elif partner_phone.type in ('5_office_fax', '4_home_fax'):
fax = num_e164
else:
phone = num_e164
partner.phone = phone
partner.mobile = mobile
partner.fax = fax
# in v10, we are supposed to have in DB E.164 format
# with the current implementation, we have:
# in res.partner : PhoneNumberFormat.INTERNATIONAL
# in res.partner.phone : E.164
# It is not good, but it is not a big bug and it's complex to fix
# so let's let it like that. In v12, we store in
# PhoneNumberFormat.INTERNATIONAL, so this bug is kind of an anticipation
# for the future :)
phone_ids = fields.One2many(
'res.partner.phone', 'partner_id', string='Phones')
phone = Phone(
compute='_compute_partner_phone', store=True, readonly=True)
compute='_compute_partner_phone',
store=True, readonly=True, compute_sudo=True)
mobile = Phone(
compute='_compute_partner_phone', store=True, readonly=True)
compute='_compute_partner_phone',
store=True, readonly=True, compute_sudo=True)
fax = Fax(
compute='_compute_partner_phone', store=True, readonly=True)
compute='_compute_partner_phone',
store=True, readonly=True, compute_sudo=True)
email = fields.Char(
compute='_compute_partner_phone',
store=True, readonly=True, compute_sudo=True)
@api.depends('phone_ids.phone', 'phone_ids.type', 'phone_ids.email')
def _compute_partner_phone(self):
for partner in self:
phone = mobile = fax = email = False
for pphone in partner.phone_ids:
if pphone.type == '1_email_primary' and pphone.email:
email = pphone.email
elif pphone.phone:
if pphone.type == '5_mobile_primary':
mobile = pphone.phone
elif pphone.type == '7_fax_primary':
fax = pphone.phone
elif pphone.type == '3_phone_primary':
phone = pphone.phone
partner.phone = phone
partner.mobile = mobile
partner.fax = fax
partner.email = email
def _update_create_vals(
self, vals, type, partner_field, partner_phone_field):
if vals.get(partner_field):
vals['phone_ids'].append(
(0, 0, {'type': type, partner_phone_field: vals[partner_field]}))
@api.model
def create(self, vals):
if 'phone_ids' not in vals:
vals['phone_ids'] = []
self._update_create_vals(vals, '1_email_primary', 'email', 'email')
self._update_create_vals(vals, '3_phone_primary', 'phone', 'phone')
self._update_create_vals(vals, '5_mobile_primary', 'mobile', 'phone')
self._update_create_vals(vals, '7_fax_primary', 'fax', 'phone')
return super(ResPartner, self).create(vals)
def _update_write_vals(
self, vals, type, partner_field, partner_phone_field):
self.ensure_one()
rppo = self.env['res.partner.phone']
if partner_field in vals:
pphone = rppo.search([
('partner_id', '=', self.id),
('type', '=', type)], limit=1)
if vals[partner_field]:
if pphone:
vals['phone_ids'].append((1, pphone.id, {
partner_phone_field: vals[partner_field]}))
else:
vals['phone_ids'].append((0, 0, {
'type': type,
partner_phone_field: vals[partner_field],
}))
else:
if pphone:
vals['phone_ids'].append((2, pphone.id))
def write(self, vals):
if 'phone_ids' not in vals:
for rec in self:
vals['phone_ids'] = []
rec._update_write_vals(vals, '1_email_primary', 'email', 'email')
rec._update_write_vals(vals, '3_phone_primary', 'phone', 'phone')
rec._update_write_vals(vals, '5_mobile_primary', 'mobile', 'phone')
rec._update_write_vals(vals, '7_fax_primary', 'fax', 'phone')
super(ResPartner, rec).write(vals)
return True
else:
return super(ResPartner, self).write(vals)

View File

@@ -14,27 +14,48 @@
<field name="name">res.partner.phone.tree</field>
<field name="model">res.partner.phone</field>
<field name="arch" type="xml">
<tree string="Phones" editable="bottom">
<tree string="Phones and E-mail" editable="bottom">
<field name="partner_id" invisible="not context.get('partner_phone_main_view')"/>
<field name="type"/>
<field name="phone" widget="phone"/>
<field name="phone" widget="phone" attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'readonly': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
<field name="email" widget="email" attrs="{'readonly': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
<field name="note"/>
</tree>
</field>
</record>
<record id="res_partner_phone_form" model="ir.ui.view">
<field name="name">res.partner.phone.form</field>
<field name="model">res.partner.phone</field>
<field name="arch" type="xml">
<form string="Phone and E-mail">
<group name="main">
<field name="partner_id" invisible="not context.get('partner_phone_main_view')"/>
<field name="type"/>
<field name="phone" widget="phone" attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'invisible': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
<field name="email" widget="email" attrs="{'invisible': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
<field name="note"/>
</group>
</form>
</field>
</record>
<record id="res_partner_phone_search" model="ir.ui.view">
<field name="name">res.partner.phone.search</field>
<field name="model">res.partner.phone</field>
<field name="arch" type="xml">
<search string="Search Phones">
<search string="Search Phones/E-mail">
<field name="phone" />
<field name="email" />
<group name="groupby">
<filter name="type_groupby" string="Type" context="{'group_by': 'type'}"/>
</group>
</search>
</field>
</record>
<record id="res_partner_phone_action" model="ir.actions.act_window">
<field name="name">Phones</field>
<field name="name">Phones/E-mails</field>
<field name="res_model">res.partner.phone</field>
<field name="view_mode">tree</field>
<field name="context">{'partner_phone_main_view': True}</field>
@@ -43,6 +64,7 @@
<menuitem id="res_partner_phone_menu" action="res_partner_phone_action"
parent="sales_team.menu_sales" sequence="10"/>
<!-- PARTNER views -->
<record id="view_partner_form" model="ir.ui.view">
<field name="name">add.phone_ids.on.partner.form</field>
<field name="model">res.partner</field>
@@ -60,14 +82,27 @@
<field name="fax" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<xpath expr="//field[@name='child_ids']/form//field[@name='phone']" position="after">
<field name="phone_ids" nolabel="1" colspan="2"/>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='phone']" position="attributes">
<field name="email" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<!-- I can't display phone_ids in the Contacts
because there is a very strange thing in the web client: if
you have a res.partner.phone on one of the fields,
it will send to write {'child_ids': [1, ID_child, {'phone_ids': [[5], [4, id_phone_child]]}]}
=> it will delete res.partner.phone and then try to re-create it,
which triggers the message 'Record does not exist or has been deleted.'
<xpath expr="//field[@name='child_ids']/form//field[@name='phone']" position="after">
<field name="phone_ids" nolabel="1" colspan="2" widget="many2many_tags"/>
</xpath>
-->
<xpath expr="//field[@name='child_ids']/form//field[@name='phone']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='mobile']" position="attributes">
<attribute name="invisible">1</attribute>
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='email']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
</field>
</record>
@@ -86,8 +121,21 @@
<field name="mobile" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="email" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>
<record id="view_res_partner_filter" model="ir.ui.view">
<field name="name">phone.one2many.res.partner.search</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base_usability.view_res_partner_filter"/>
<field name="arch" type="xml">
<field name="name" position="attributes">
<attribute name="filter_domain">['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute>
</field>
</field>
</record>
</odoo>

View File

@@ -21,26 +21,33 @@ def create_partner_phone(cr, phone_field, phone_type):
return to_create
def create_partner_email(cr):
cr.execute('SELECT id, email FROM res_partner WHERE email IS NOT null')
to_create = []
for partner in cr.fetchall():
to_create.append({
'partner_id': partner[0],
'type': '1_email_primary',
'email': partner[1],
})
return to_create
def migrate_to_partner_phone(cr, registry):
"""This post_install script is required because, when the module
is installed, Odoo creates the column in the DB and compute the field
and THEN it loads the file data/res_country_department_data.yml...
So, when it computes the field on module installation, the
departments are not available in the DB, so the department_id field
on res.partner stays null. This post_install script fixes this."""
logger.info('start data migration for one2many_phone')
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
rppo = env['res.partner.phone']
to_create = []
to_create += create_partner_phone(cr, 'phone', '1_home')
to_create += create_partner_phone(cr, 'mobile', '2_mobile')
to_create += create_partner_phone(cr, 'fax', '5_office_fax')
to_create += create_partner_phone(cr, 'phone', '3_phone_primary')
to_create += create_partner_phone(cr, 'mobile', '5_mobile_primary')
to_create += create_partner_phone(cr, 'fax', '7_fax_primary')
to_create += create_partner_email(cr)
# I need to create all at the end for invalidation purposes
for vals in to_create:
rppo.create(vals)
logger.info(
'partner_phone type %s phone %s created for partner ID %d',
vals['type'], vals['phone'], vals['partner_id'])
'partner_phone type %s phone %s email %s created for partner ID %d',
vals['type'], vals.get('phone'), vals.get('mail'), vals['partner_id'])
logger.info('end data migration for one2many_phone')
return

View File

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

View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Barroux Abbey
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
class TestPartnerPhone(TransactionCase):
def setUp(self):
super(TestPartnerPhone, self).setUp()
def _check_result(self, partner, result):
rppo = self.env['res.partner.phone']
pphone_email = rppo.search(
[('type', '=', '1_email_primary'), ('partner_id', '=', partner.id)])
if result['email']:
self.assertEquals(partner.email, result['email'])
self.assertEquals(len(pphone_email), 1)
self.assertEquals(pphone_email.email, result['email'])
else:
self.assertFalse(partner.email)
self.assertFalse(pphone_email)
if result['phone']:
self.assertEquals(partner.phone.replace(u'\xa0', ''), result['phone'])
else:
self.assertFalse(partner.phone)
if result['mobile']:
self.assertEquals(partner.mobile.replace(u'\xa0', ''), result['mobile'])
else:
self.assertFalse(partner.mobile)
if result['fax']:
self.assertEquals(partner.fax.replace(u'\xa0', ''), result['fax'])
else:
self.assertFalse(partner.fax)
field2type = {
'phone': '3_phone_primary',
'mobile': '5_mobile_primary',
'fax': '7_fax_primary',
}
for field, value in result.items():
if field in field2type:
type = field2type[field]
pphone = rppo.search(
[('type', '=', type), ('partner_id', '=', partner.id)])
if value:
self.assertEquals(len(pphone), 1)
self.assertEquals(pphone.phone.replace(u'\xa0', ''), value)
else:
self.assertFalse(pphone)
def test_create_partner(self):
rpo = self.env['res.partner']
p = rpo.create({
'name': 'Test Me',
'email': 'testme@example.com',
'phone': '0198089246',
'mobile': '0198089247',
'fax': '0198089248',
})
result = {
'email': 'testme@example.com',
'phone': '+33198089246',
'mobile': '+33198089247',
'fax': '+33198089248',
}
self._check_result(p, result)
p2 = rpo.create({
'name': 'Test me now',
'email': 'testmenow@example.com',
'phone': '0972727272',
})
result = {
'email': 'testmenow@example.com',
'phone': '+33972727272',
'mobile': False,
'fax': False,
}
self._check_result(p2, result)
p3 = rpo.create({
'name': 'Test me now',
'phone_ids': [
(0, 0, {'type': '3_phone_primary', 'phone': '0972727272'}),
(0, 0, {'type': '1_email_primary', 'email': 'tutu@example.fr'})],
})
result = {
'email': 'tutu@example.fr',
'phone': '+33972727272',
'mobile': False,
'fax': False,
}
self._check_result(p3, result)
def test_write_partner(self):
p1 = self.env['res.partner'].create({
'name': 'test me now',
'country_id': self.env.ref('base.fr').id,
})
result_none = {
'email': False,
'phone': False,
'mobile': False,
'fax': False,
}
self._check_result(p1, result_none)
p1.write({
'mobile': '0198089247',
'email': 'testmenow@example.com',
})
result = {
'email': 'testmenow@example.com',
'phone': False,
'mobile': '+33198089247',
'fax': False,
}
self._check_result(p1, result)
p1.write({
'email': 'testmenow2@example.com',
'phone': False,
'mobile': '04.72.72.72.72',
})
result = {
'email': 'testmenow2@example.com',
'phone': False,
'mobile': '+33472727272',
'fax': False,
}
self._check_result(p1, result)
p1.write({
'phone': False,
'mobile': False,
'email': False,
})
self._check_result(p1, result_none)
p2 = self.env['res.partner'].create({'name': 'Toto', 'email': 'toto@example.com'})
p_multi = p1 + p2
p_multi.write({'email': 'all@example.com', 'phone': '05.60.60.60.70'})
result = {
'email': 'all@example.com',
'phone': '+33560606070',
'mobile': False,
'fax': False,
}
self._check_result(p1, result)
self._check_result(p2, result)

View File

@@ -33,7 +33,7 @@
<field name="name">Prospects</field>
<field name="res_model">res.partner</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context">{'default_prospect': 1, 'search_default_prospect': 1}</field>
<field name="context">{'default_prospect': 1, 'default_customer': False, 'search_default_prospect': 1}</field>
</record>
<!-- I don't add a menu entry ; it should be added in custom module if needed -->

View File

@@ -2,7 +2,7 @@
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo import models, fields, api
class ResPartner(models.Model):
@@ -16,6 +16,15 @@ class ResPartner(models.Model):
'A partner already exists with this internal reference!'
)]
# in v10, display_name is store=True by default
# so, when we inherit name_get() and use additionnal fields, we
# have to inherit @api.depends of _compute_display_name() too
@api.depends(
'is_company', 'name', 'parent_id.name', 'type', 'company_name',
'ref', 'parent_id.ref')
def _compute_display_name(self):
super(ResPartner, self)._compute_display_name()
@api.multi
def name_get(self):
res = []

View File

@@ -2,6 +2,7 @@
from . import users
from . import partner
from . import bank
from . import company
from . import mail
from . import misc

View File

@@ -31,6 +31,7 @@ A group by 'State' is added to module search view.
'security/group.xml',
'security/ir.model.access.csv',
'partner_view.xml',
'partner_bank_view.xml',
'users_view.xml',
'country_view.xml',
'module_view.xml',

20
base_usability/bank.py Normal file
View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, api
class ResBank(models.Model):
_inherit = 'res.bank'
@api.multi
@api.depends('name', 'bic')
def name_get(self):
result = []
for bank in self:
name = bank.name
if bank.bic:
name = u'[%s] %s' % (bank.bic, name)
result.append((bank.id, name))
return result

View File

@@ -73,10 +73,16 @@ class ResPartner(models.Model):
if self.is_company:
company = self.name
name = False
name_no_title = False
title = False
title_short = False
else:
name = self.name_title
company = self.parent_id and self.parent_id.is_company and\
self.parent_id.name or False
name = self.name_title
name_no_title = self.name
title = self.title.name
title_short = self.title.shortcut
options = {
'name': {
'value': name,
@@ -84,6 +90,15 @@ class ResPartner(models.Model):
'company': {
'value': company,
},
'title': {
'value': title,
},
'title_short': {
'value': title_short,
},
'name_no_title': {
'value': name_no_title,
},
'phone': {
'value': self.phone,
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 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_bank_tree" model="ir.ui.view">
<field name="name">base_usability.res.partner.bank.tree</field>
<field name="model">res.partner.bank</field>
<field name="inherit_id" ref="base.view_partner_bank_tree"/>
<field name="arch" type="xml">
<field name="sequence" position="attributes">
<attribute name="invisible">0</attribute>
<attribute name="widget">handle</attribute>
</field>
<field name="bank_name" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="bank_name" position="after">
<field name="bank_id"/>
</field>
</field>
</record>
</odoo>

View File

@@ -19,6 +19,10 @@
<xpath expr="//field[@name='child_ids']/form//field[@name='email']" position="attributes">
<attribute name="widget">email</attribute>
</xpath>
<!-- Show title not only on Contacts -->
<xpath expr="//field[@name='child_ids']/form//field[@name='title']" position="attributes">
<attribute name="attrs"></attribute>
</xpath>
</field>
</record>
@@ -41,6 +45,11 @@
<field name="country_id" position="attributes">
<attribute name="invisible">0</attribute>
</field>
<!-- There aren't many fields in this tree view, so there is room
to add a few more -->
<field name="phone" position="after">
<field name="mobile"/>
</field>
<field name="country_id" position="before">
<field name="city"/>
</field>

View File

@@ -22,6 +22,7 @@ def formatLang(
if (
'base.usability.installed' in env and
int_no_digits and
not monetary and
isinstance(value, float) and
dp):
prec = env['decimal.precision'].precision_get(dp)
@@ -33,4 +34,5 @@ def formatLang(
grouping=grouping, monetary=monetary, dp=dp, currency_obj=currency_obj)
return res
report_sxw.rml_parse.formatLang = formatLang

View File

@@ -25,7 +25,7 @@ class ResUsers(models.Model):
@api.model
def _script_partners_linked_to_users_no_company(self):
if self.env.user.id != SUPERUSER_ID:
raise UserError(_('You must run this script as admin user'))
self = self.sudo()
logger.info(
'START to set company_id=False on partners related to users')
users = self.search(

View File

@@ -0,0 +1,37 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
========================
User Authentication Logs
========================
This module adds user authentication logs in Odoo. It logs both authentication success and failures.
Usage
=====
The authentication logs can be seen:
* on the users's form view in the *Auth Logs* tab,
* in the menu *Settings > Technical > Security > Authentication Logs*.
Authentication failure logs are displayed in red. Authentication success logs are displayed in black.
To have read access to the logs, you need to be part of the *Access Rights* group.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/akretion/odoo-usability/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Alexis de Lattre <alexis.delattre@akretion.com>

View File

@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import report
from . import models

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Akretion France
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Users authentification logs',
'version': '10.0.1.0.0',
'category': 'Tools',
'license': 'AGPL-3',
'summary': 'Adds users authentication logs',
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/res_users_auth_log.xml',
'views/res_users.xml',
'data/ir_cron.xml',
],
'installable': True,
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017-2018 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<record id="purge_auth_log_cron" model="ir.cron">
<field name="name">Purge old authentication logs</field>
<field name="active" eval="True"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">months</field>
<field name="numbercall">-1</field> <!-- don't limit the number of calls -->
<field name="doall" eval="False"/>
<field name="model" eval="'res.users.auth.log'"/>
<field name="function" eval="'_purge_old_auth_logs'" />
<field name="args" eval="'()'"/>
</record>
</odoo>

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import res_users_auth_log
from . import res_users

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Akretion France
# @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, registry, SUPERUSER_ID
import logging
logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
auth_log_ids = fields.One2many(
'res.users.auth.log', 'user_id', string='Authentication Logs')
@classmethod
def _login(cls, db, login, password):
user_id = super(ResUsers, cls)._login(db, login, password)
with registry(db).cursor() as cr:
if user_id:
result = 'success'
user_log_id = user_id
else:
# To write a null value, psycopg2 wants None
user_log_id = None
result = 'failure'
cr.execute(
"SELECT id FROM res_users WHERE login=%s", (login, ))
user_select = cr.fetchall()
if user_select:
user_log_id = user_select[0][0]
cr.execute("""
INSERT INTO res_users_auth_log (
create_uid,
create_date,
date,
login,
result,
user_id
) VALUES (
%s, NOW() AT TIME ZONE 'UTC', NOW() AT TIME ZONE 'UTC',
%s, %s, %s)""", (SUPERUSER_ID, login, result, user_log_id))
logger.info('Auth log created for login %s type %s', login, result)
return user_id

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from datetime import datetime, timedelta
import logging
logger = logging.getLogger(__name__)
class ResUsersAuthLog(models.Model):
_name = 'res.users.auth.log'
_description = 'Users Authentication Logs'
_order = 'date desc'
_rec_name = 'date'
user_id = fields.Many2one(
'res.users', string='User', ondelete='cascade', readonly=True)
login = fields.Char(string='Login', readonly=True)
date = fields.Datetime(
string='Authentication Date', required=True, readonly=True)
result = fields.Selection([
('success', 'Success'),
('failure', 'Failure'),
], string='Result', required=True, readonly=True)
@api.model
def create(self, vals):
if not self._context.get('authenticate_create'):
raise UserError(_(
"You cannot manually create an authentication log."))
return super(ResUsersAuthLog, self).create(vals)
@api.multi
def write(self, vals):
raise UserError(_("You cannot modify an authentication log."))
@api.model
def _purge_old_auth_logs(self):
expiry_date = datetime.today() - timedelta(days=365)
self._cr.execute(
"DELETE FROM res_users_auth_log WHERE date <= %s", (expiry_date, ))
logger.info('Auth logs older than %s have been purged', expiry_date)

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_res_users_auth_log,Read access to Access rights group,model_res_users_auth_log,base.group_erp_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_res_users_auth_log Read access to Access rights group model_res_users_auth_log base.group_erp_manager 1 0 0 0

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017-2018 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="name">auth_logs.res.users.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Auth Logs" name="auth_logs">
<field name="auth_log_ids" nolabel="1"/>
</page>
</notebook>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017-2018 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="res_users_auth_log_form" model="ir.ui.view">
<field name="name">res.users.auth.logs.form</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<form string="Authentication Log">
<group name="main">
<field name="date"/>
<field name="user_id"/>
<field name="login"/>
<field name="result"/>
</group>
</form>
</field>
</record>
<record id="res_users_auth_log_tree" model="ir.ui.view">
<field name="name">res.users.auth.logs.tree</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<tree string="Authentication Logs" colors="red:result=='failure'">
<field name="date"/>
<field name="user_id" invisible="not context.get('auth_logs_main_view')"/>
<field name="login" invisible="not context.get('auth_logs_main_view')"/>
<field name="result"/>
</tree>
</field>
</record>
<record id="res_users_auth_log_search" model="ir.ui.view">
<field name="name">res.users.auth.logs.search</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<search string="Search Authentication Logs">
<field name="user_id"/>
<filter name="success" string="Success" domain="[('result', '=', 'success')]"/>
<filter name="failure" string="Failure" domain="[('result', '=', 'failure')]"/>
<group string="Group By" name="groupby">
<filter name="day_groupby" string="Day" context="{'group_by': 'date:day'}"/>
<filter name="week_groupby" string="Week" context="{'group_by': 'date:week'}"/>
<filter name="month_groupby" string="Month" context="{'group_by': 'date:month'}"/>
<filter name="user_groupby" string="User" context="{'group_by': 'user_id'}"/>
<filter name="result_groupby" string="Result" context="{'group_by': 'result'}"/>
</group>
</search>
</field>
</record>
<record id="res_users_auth_log_graph" model="ir.ui.view">
<field name="name">res.users.auth.logs.graph</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<graph string="Analyze Authentication Logs" type="pivot">
<field name="date" type="row" interval="week"/>
<field name="user_id" type="col"/>
</graph>
</field>
</record>
<record id="res_users_auth_log_action" model="ir.actions.act_window">
<field name="name">Authentication Logs</field>
<field name="res_model">res.users.auth.log</field>
<field name="view_mode">tree,form,graph</field>
<field name="context">{'auth_logs_main_view': True}</field>
</record>
<menuitem id="res_users_auth_log_menu" action="res_users_auth_log_action"
parent="base.menu_security" sequence="100"/>
</odoo>

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import models
from . import wizard

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# 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).
{
'name': 'Commission Simple',
'version': '10.0.1.0.0',
'category': 'Sales',
'license': 'AGPL-3',
'summary': 'Compute commissions for salesman',
'description': """
Commission Simple
=================
This module is a **simple** module to compute commission for salesman. From my experience, companies often use very specific methods to compute commissions and it's impossible to develop a module that can support all of them. So the goal of this module is just to have a simple base to build the company-specific commissionning system by inheriting this simple module.
Here is a short description of this module:
* create commission profiles using rules (per product category, per product, per product and customer, etc.),
* the commission rules can have a start and end date (optional),
* commissionning can happen on invoicing or on payment,
* each invoice line can only be commissionned to one salesman,
* commission reports are stored in Odoo.
This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': [
'account',
'date_range',
# this uses some related fields on account.invoice.line
'account_usability',
],
'data': [
'data/decimal_precision.xml',
'views/commission.xml',
'views/res_users.xml',
'views/account_config_settings.xml',
'wizard/commission_compute_view.xml',
'security/ir.model.access.csv',
'security/rule.xml',
],
'installable': True,
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record forcecreate="True" id="commission_rate" model="decimal.precision">
<field name="name">Commission Rate</field>
<field name="digits">2</field>
</record>
</odoo>

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import commission
from . import res_users
from . import res_company
from . import account_config_settings
from . import account_invoice_line

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# 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 fields, models
class AccountConfigSettings(models.TransientModel):
_inherit = 'account.config.settings'
commission_date_range_type_id = fields.Many2one(
related='company_id.commission_date_range_type_id', readonly=False)

View File

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# 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, fields, models
import odoo.addons.decimal_precision as dp
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
user_id = fields.Many2one(
related='invoice_id.user_id', store=True, readonly=True)
product_categ_id = fields.Many2one(
related='product_id.product_tmpl_id.categ_id', store=True, readonly=True)
commission_result_id = fields.Many2one(
'commission.result', string='Commission Result')
commission_rule_id = fields.Many2one(
'commission.rule', 'Matched Commission Rule', ondelete='restrict')
commission_base = fields.Monetary('Commission Base', currency_field='company_currency_id')
commission_rate = fields.Float('Commission Rate', digits=dp.get_precision('Commission Rate'))
commission_amount = fields.Monetary(
string='Commission Amount', currency_field='company_currency_id',
readonly=True, compute='_compute_commission_amount', store=True)
@api.depends('commission_rate', 'commission_base')
def _compute_commission_amount(self):
for line in self:
line.commission_amount = line.company_currency_id.round(
line.commission_rate * line.commission_base / 100.0)
def compute_commission_for_one_user(self, user, date_range, rules):
profile = user.commission_profile_id
company = profile.company_id
company_currency = company.currency_id
assert profile
domain = [
('invoice_type', 'in', ('out_invoice', 'out_refund')),
('date_invoice', '<=', date_range.date_end),
('company_id', '=', company.id),
('user_id', '=', user.id),
('commission_result_id', '=', False),
]
if profile.trigger_type == 'invoice':
domain.append(('state', 'in', ('open', 'paid')))
elif profile.trigger_type == 'payment':
# TODO : for this trigger, we would need to filter
# out the invoices paid after the end date of the period compute
domain.append(('state', '=', 'paid'))
else:
raise
ilines = self.search(domain, order='date_invoice, invoice_id, sequence')
com_result = self.env['commission.result'].create({
'user_id': user.id,
'profile_id': profile.id,
'date_range_id': date_range.id,
})
total = 0.0
for iline in ilines:
rule = iline._match_commission_rule(rules[profile.id])
if rule:
lvals = iline._prepare_commission_data(rule, com_result)
if lvals:
iline.write(lvals)
total += company_currency.round(
lvals['commission_rate'] * lvals['commission_base']
/ 100.0)
com_result.amount_total = total
return com_result
def _match_commission_rule(self, rules):
# commission rules are already in the right order
self.ensure_one()
for rule in rules:
if rule['date_start'] and rule['date_start'] > self.date_invoice:
continue
if rule['date_end'] and rule['date_end'] < self.date_invoice:
continue
if rule['applied_on'] == '0_customer_product':
if (
self.commercial_partner_id.id in
rule['partner_ids'] and
self.product_id.id in rule['product_ids']):
return rule
elif rule['applied_on'] == '1_customer_product_category':
if (
self.commercial_partner_id.id in
rule['partner_ids'] and
self.product_categ_id.id in rule['product_categ_ids']):
return rule
elif rule['applied_on'] == '2_product':
if self.product_id.id in rule['product_ids']:
return rule
elif rule['applied_on'] == '3_product_category':
if self.product_categ_id.id in rule['product_categ_ids']:
return rule
elif rule['applied_on'] == '4_global':
return rule
return False
def _prepare_commission_data(self, rule, commission_result):
self.ensure_one()
lvals = {
'commission_result_id': commission_result.id,
'commission_rule_id': rule['id'],
# company currency
'commission_base': self.price_subtotal_signed,
'commission_rate': rule['rate'],
}
return lvals

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
# Copyright Akretion France (http://www.akretion.com/)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, api
import odoo.addons.decimal_precision as dp
class CommissionProfile(models.Model):
_name = 'commission.profile'
_description = 'Commission Profile'
name = fields.Char(string='Name of the Profile', required=True)
active = fields.Boolean(string='Active', default=True)
company_id = fields.Many2one(
'res.company', string='Company',
required=True,
default=lambda self: self.env['res.company']._company_default_get())
line_ids = fields.One2many(
'commission.rule', 'profile_id', string='Commission Rules')
trigger_type = fields.Selection([
('invoice', 'Invoicing'),
('payment', 'Payment'),
], default='invoice', string='Trigger', required=True)
class CommissionRule(models.Model):
_name = 'commission.rule'
_description = 'Commission Rule'
_order = 'profile_id, applied_on'
partner_ids = fields.Many2many(
'res.partner', string='Customers',
domain=[('parent_id', '=', False), ('customer', '=', True)])
product_categ_ids = fields.Many2many(
'product.category', string="Product Categories",
domain=[('type', '=', 'normal')])
product_ids = fields.Many2many('product.product', string='Products')
date_start = fields.Date('Start Date')
date_end = fields.Date('End Date')
profile_id = fields.Many2one(
'commission.profile', string='Profile', ondelete='cascade')
company_id = fields.Many2one(
related='profile_id.company_id', store=True, readonly=True)
rate = fields.Float(
'Commission Rate', digits=dp.get_precision('Commission Rate'),
copy=False)
applied_on = fields.Selection([
('0_customer_product', 'Products and Customers'),
('1_customer_product_category', "Product Categories and Customers"),
('2_product', "Products"),
('3_product_category', "Product Categories"),
('4_global', u'Global')],
string='Apply On', default='4_global', required=True)
active = fields.Boolean(string='Active', default=True)
@api.model
def load_all_rules(self):
rules = self.search_read()
res = {} # key = profile, value = [rule1 recordset, rule2]
for rule in rules:
if rule['profile_id']:
if rule['profile_id'][0] not in res:
res[rule['profile_id'][0]] = [rule]
else:
res[rule['profile_id'][0]].append(rule)
return res
_sql_constraints = [(
'rate_positive',
'CHECK(rate >= 0)',
'Rate must be positive !')]
class CommissionResult(models.Model):
_name = 'commission.result'
_description = "Commission Result"
_order = 'date_start desc'
user_id = fields.Many2one(
'res.users', 'Salesman', required=True, ondelete='restrict',
readonly=True)
profile_id = fields.Many2one(
'commission.profile', string='Commission Profile',
readonly=True)
company_id = fields.Many2one(
'res.company', string='Company',
required=True, readonly=True,
default=lambda self: self.env['res.company']._company_default_get())
company_currency_id = fields.Many2one(
related='company_id.currency_id', string='Company Currency',
readonly=True, store=True)
date_range_id = fields.Many2one(
'date.range', required=True, string='Period', readonly=True)
date_start = fields.Date(
related='date_range_id.date_start', readonly=True, store=True)
date_end = fields.Date(
related='date_range_id.date_end', readonly=True, store=True)
line_ids = fields.One2many(
'account.invoice.line', 'commission_result_id', 'Commission Lines',
readonly=True)
amount_total = fields.Monetary(
string='Commission Total', currency_field='company_currency_id',
help='This is the total amount at the date of the computation of the commission',
readonly=True)
def name_get(self):
res = []
for result in self:
name = '%s (%s)' % (result.user_id.name, result.date_range_id.name)
res.append((result.id, name))
return res
_sql_constraints = [(
'salesman_period_company_unique',
'unique(company_id, user_id, date_range_id)',
'A commission result already exists for this salesman for '
'the same period')]

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# 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 fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
commission_date_range_type_id = fields.Many2one(
'date.range.type', string='Commission Periodicity')

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# 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 fields, models
class ResUsers(models.Model):
_inherit = 'res.users'
commission_profile_id = fields.Many2one(
'commission.profile', string='Commission Profile',
company_dependant=True)

View File

@@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_commission_profile_read,Read access on commission.profile for employees,model_commission_profile,base.group_user,1,0,0,0
access_commission_profile_full,Full access on commission.profile for financial manager,model_commission_profile,account.group_account_manager,1,1,1,1
access_commission_rule_full,Full access on commission.rule for financial manager,model_commission_rule,account.group_account_manager,1,1,1,1
access_commission_rule_read,Read access on commission.rule for invoicing group,model_commission_rule,account.group_account_invoice,1,0,0,0
access_commission_result_full,Full access on commission.result to accountant,model_commission_result,account.group_account_user,1,1,1,1
access_commission_result_read,Read access on commission.result to invoicing grp,model_commission_result,account.group_account_invoice,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_commission_profile_read Read access on commission.profile for employees model_commission_profile base.group_user 1 0 0 0
3 access_commission_profile_full Full access on commission.profile for financial manager model_commission_profile account.group_account_manager 1 1 1 1
4 access_commission_rule_full Full access on commission.rule for financial manager model_commission_rule account.group_account_manager 1 1 1 1
5 access_commission_rule_read Read access on commission.rule for invoicing group model_commission_rule account.group_account_invoice 1 0 0 0
6 access_commission_result_full Full access on commission.result to accountant model_commission_result account.group_account_user 1 1 1 1
7 access_commission_result_read Read access on commission.result to invoicing grp model_commission_result account.group_account_invoice 1 0 0 0

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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)
-->
<odoo noupdate="1">
<record id="commission_profile_rule" model="ir.rule">
<field name="name">Commission Profile multi-company</field>
<field name="model_id" ref="model_commission_profile"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="commission_rule_rule" model="ir.rule">
<field name="name">Commission Rule multi-company</field>
<field name="model_id" ref="model_commission_rule"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="commission_result_rule" model="ir.rule">
<field name="name">Commission Result multi-company</field>
<field name="model_id" ref="model_commission_result"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
</odoo>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_account_config_settings" model="ir.ui.view">
<field name="name">commission.account.config.settings.form</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings" />
<field name="arch" type="xml">
<xpath expr="//div[@name='invoice_taxes']/.." position="after">
<group name="commission">
<label for="id" string="Commission"/>
<div name="commission">
<div name="commission_date_range_type">
<label for="commission_date_range_type_id"/>
<field name="commission_date_range_type_id" class="oe_inline"/>
</div>
</div>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,214 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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)
-->
<odoo>
<menuitem id="commission_root" name="Commissions" parent="account.menu_finance" sequence="11"/>
<menuitem id="commission_config_root" name="Commissions" parent="account.menu_finance_configuration" sequence="110"/>
<!-- PROFILE -->
<record id="commission_profile_form" model="ir.ui.view">
<field name="name">commission.profile.form</field>
<field name="model">commission.profile</field>
<field name="arch" type="xml">
<form string="Commission Profile">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group name="main">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="trigger_type"/>
</group>
<group name="lines" string="Rules">
<field name="line_ids" nolabel="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="commission_profile_tree" model="ir.ui.view">
<field name="name">commission.profile.tree</field>
<field name="model">commission.profile</field>
<field name="arch" type="xml">
<tree string="Commission Profiles">
<field name="name"/>
<field name="trigger_type"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="commission_profile_action" model="ir.actions.act_window">
<field name="name">Commission Profiles</field>
<field name="res_model">commission.profile</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="commission_profile_menu" action="commission_profile_action" parent="commission_config_root" sequence="18"/>
<!-- RULE -->
<record id="commission_rule_form" model="ir.ui.view">
<field name="name">commission.rule.form</field>
<field name="model">commission.rule</field>
<field name="arch" type="xml">
<form string="Commission Rules">
<sheet>
<group name="main">
<field name="profile_id" invisible="not context.get('commission_rule_main_view')"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="applied_on" widget="radio"/>
</group>
<group name="match" string="Match">
<field name="partner_ids" attrs="{'invisible': [('applied_on', 'not in', ('0_customer_product', '1_customer_product_category'))], 'required': [('applied_on', 'in', ('0_customer_product', '1_customer_product_category'))]}"/>
<field name="product_categ_ids" attrs="{'invisible': [('applied_on', 'not in', ('1_customer_product_category', '3_product_category'))], 'required': [('applied_on', 'in', ('1_customer_product_category', '3_product_category'))]}"/>
<field name="product_ids" attrs="{'invisible': [('applied_on', 'not in', ('0_customer_product', '2_product'))], 'required': [('applied_on', 'in', ('0_customer_product', '2_product'))]}"/>
<field name="date_start"/>
<field name="date_end"/>
</group>
<group name="compute" string="Compute">
<label for="rate"/>
<div name="rate">
<field name="rate" class="oe_inline"/> %
</div>
</group>
</sheet>
</form>
</field>
</record>
<record id="commission_rule_tree" model="ir.ui.view">
<field name="name">commission.rule.tree</field>
<field name="model">commission.rule</field>
<field name="arch" type="xml">
<tree string="Commission Rules">
<field name="profile_id" invisible="not context.get('commission_rule_main_view')"/>
<field name="applied_on"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="rate" string="Rate (%)"/>
</tree>
</field>
</record>
<record id="commission_rule_search" model="ir.ui.view">
<field name="name">commission.rule.search</field>
<field name="model">commission.rule</field>
<field name="arch" type="xml">
<search string="Search in Commission Rules">
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group name="groupby">
<filter name="profile_groupby" string="Profile" context="{'group_by': 'profile_id'}"/>
</group>
</search>
</field>
</record>
<record id="commission_rule_action" model="ir.actions.act_window">
<field name="name">Commission Rules</field>
<field name="res_model">commission.rule</field>
<field name="view_mode">tree,form</field>
<field name="context">{'commission_rule_main_view': True}</field>
</record>
<menuitem id="commission_rule_menu" action="commission_rule_action" parent="commission_config_root" sequence="20"/>
<!-- RESULT -->
<record id="commission_result_form" model="ir.ui.view">
<field name="name">commission.result.form</field>
<field name="model">commission.result</field>
<field name="arch" type="xml">
<form string="Commission Result">
<group name="main">
<group name="main-left">
<field name="user_id"/>
<field name="profile_id" groups="account.group_account_manager"/>
<field name="company_currency_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="amount_total"/>
</group>
<group name="main-right">
<field name="date_range_id"/>
<field name="date_start"/>
<field name="date_end"/>
</group>
</group>
<group name="lines" string="Invoice Lines">
<field nolabel="1" name="line_ids">
<tree>
<field name="invoice_number"/>
<field name="date_invoice"/>
<field name="commercial_partner_id" string="Customer"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id"/>
<field name="price_unit"/>
<field name="currency_id"/>
<field name="discount"/>
<field name="price_subtotal_signed" string="Amount w/o tax in company cur."/>
<field name="state"/>
<field name="commission_base"/>
<field name="commission_rate" string="Rate (%)"/>
<field name="commission_amount" sum="1"/>
<field name="commission_rule_id" string="Commission Rule"/>
<field name="company_currency_id" invisible="1"/>
</tree>
</field>
</group>
</form>
</field>
</record>
<record id="commission_result_tree" model="ir.ui.view">
<field name="name">commission.result.tree</field>
<field name="model">commission.result</field>
<field name="arch" type="xml">
<tree string="Commission Results">
<field name="date_range_id"/>
<field name="user_id"/>
<field name="profile_id" groups="account.group_account_manager"/>
<field name="company_currency_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="amount_total" sum="Total"/>
</tree>
</field>
</record>
<record id="commission_result_search" model="ir.ui.view">
<field name="name">commission.result.search</field>
<field name="model">commission.result</field>
<field name="arch" type="xml">
<search string="Search Commission Results">
<field name="user_id"/>
<field name="date_range_id"/>
<group name="groupby">
<filter name="user_groupby" string="Salesman" context="{'group_by': 'user_id'}"/>
<filter name="date_range_groupby" string="Period" context="{'group_by': 'date_range_id'}"/>
</group>
</search>
</field>
</record>
<record id="commission_result_action" model="ir.actions.act_window">
<field name="name">Commissions</field>
<field name="res_model">commission.result</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="commission_result_menu" action="commission_result_action" parent="commission_root" sequence="10"/>
</odoo>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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)
-->
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="name">commission.res.users.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='action_id']/.." position="after">
<group name="commission" string="Commission">
<field name="commission_profile_id"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import commission_compute

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# 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, fields, models, _
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError
import logging
logger = logging.getLogger(__name__)
class CommissionCompute(models.TransientModel):
_name = 'commission.compute'
_description = 'Compute Commissoins'
@api.model
def _default_date_range(self):
drange_type = self.env.user.company_id.commission_date_range_type_id
if not drange_type:
return False
today = fields.Date.from_string(fields.Date.context_today(self))
first_day_last_month = today + relativedelta(months=-1, day=1)
dranges = self.env['date.range'].search([
'|', ('company_id', '=', self.env.user.company_id.id),
('company_id', '=', False),
('type_id', '=', drange_type.id),
('date_start', '=', fields.Date.to_string(first_day_last_month))
])
return dranges and dranges[0] or dranges
date_range_id = fields.Many2one(
'date.range', required=True, string='Period',
default=lambda self: self._default_date_range())
date_start = fields.Date(related='date_range_id.date_start', readonly=True)
date_end = fields.Date(related='date_range_id.date_end', readonly=True)
def run(self):
self.ensure_one()
creso = self.env['commission.result']
ruo = self.env['res.users']
date_range = self.date_range_id
existing_res = creso.search([('date_range_id', '=', date_range.id)])
if existing_res:
raise UserError(
u'Il existe déjà des commissions pour cette période.')
com_result_ids = self.core_compute()
if not com_result_ids:
raise UserError(_('No commission generated.'))
action = self.env['ir.actions.act_window'].for_xml_id(
'commission_simple', 'commission_result_action')
action.update({
'views': False,
'domain': "[('id', 'in', %s)]" % com_result_ids,
})
return action
def core_compute(self):
rules = self.env['commission.rule'].load_all_rules()
ailo = self.env['account.invoice.line']
ruo = self.env['res.users']
com_result_ids = []
for user in ruo.with_context(active_test=False).search([]):
if user.commission_profile_id:
if user.commission_profile_id.id not in rules:
raise UserError(_(
"The commission profile '%s' doesn't have any rules.")
% user.commission_profile_id.name)
com_result = ailo.compute_commission_for_one_user(user, self.date_range_id, rules)
if com_result:
com_result_ids.append(com_result.id)
else:
logger.debug(
"Commission computation: salesman '%s' "
"doesn't have a commission profile",
user.name)
return com_result_ids

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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).
-->
<odoo>
<record id="commission_compute_form" model="ir.ui.view">
<field name="name">commission.compute.form</field>
<field name="model">commission.compute</field>
<field name="arch" type="xml">
<form string="Compute Commissions">
<group name="main">
<field name="date_range_id"/>
<field name="date_start"/>
<field name="date_end"/>
</group>
<footer>
<button name="run" type="object" string="Compute"
class="btn-primary"/>
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<record id="commission_compute_action" model="ir.actions.act_window">
<field name="name">Compute Commissions</field>
<field name="res_model">commission.compute</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="commission_compute_menu" action="commission_compute_action" parent="commission_root" sequence="15" groups="account.group_account_user"/>
</odoo>

View File

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# 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).
{
'name': 'Commission Simple Sale',
'version': '10.0.1.0.0',
'category': 'Sales',
'license': 'AGPL-3',
'summary': 'Give access to commission results to Salesman',
'description': """
Commission Simple Sale
======================
This module allows salesman to see their commissions in Odoo, under the Sales menu.
This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': [
'sale',
'commission_simple',
],
'data': [
'views/commission.xml',
'security/rule.xml',
'security/ir.model.access.csv',
],
'installable': True,
}

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_commission_result_salesman_read,Read access on commission.result to salesman,commission_simple.model_commission_result,sales_team.group_sale_salesman,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_commission_result_salesman_read Read access on commission.result to salesman commission_simple.model_commission_result sales_team.group_sale_salesman 1 0 0 0

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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)
-->
<odoo noupdate="1">
<record id="commission_result_salesman_rule" model="ir.rule">
<field name="name">Commission Result for Salesman</field>
<field name="model_id" ref="commission_simple.model_commission_result"/>
<field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
<record id="commission_result_see_all_rule" model="ir.rule">
<field name="name">Commission Result for Sales Manager (see all)</field>
<field name="model_id" ref="commission_simple.model_commission_result"/>
<field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
</odoo>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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)
-->
<odoo>
<menuitem id="commission_sale_root" name="Commissions" parent="sales_team.menu_base_partner" sequence="50"/>
<menuitem id="commission_result_sale_menu" action="commission_simple.commission_result_action" parent="commission_sale_root" sequence="10" groups="sales_team.group_sale_salesman"/>
</odoo>

View File

@@ -10,7 +10,8 @@ class CrmLead(models.Model):
@api.multi
def _lead_create_contact(self, name, is_company, parent_id=False):
partner = super(CrmLead, self)._lead_create_contact(
self_ctx = self.with_context(
default_customer=False, default_prospect=True)
partner = super(CrmLead, self_ctx)._lead_create_contact(
name, is_company, parent_id=parent_id)
partner.write({'prospect': True, 'customer': False})
return partner

View File

@@ -26,6 +26,7 @@ This module has been written by Alexis de Lattre from Akretion
'depends': ['crm'],
'data': [
#'wizard/base_partner_merge_view.xml',
'security/crm_security.xml',
'crm_view.xml',
],
'installable': True,

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<record id="crm_lead_multi_company_rule" model="ir.rule">
<field name="name">CRM Lead multi-company</field>
<field name="model_id" ref="model_crm_lead"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
</odoo>

View File

@@ -1,2 +1,4 @@
# -*- coding: utf-8 -*-
from . import stock
from . import sale_report

View File

@@ -22,8 +22,10 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
'website': 'http://www.akretion.com',
'depends': ['delivery'],
'data': [
'security/ir.model.access.csv',
'delivery_view.xml',
'sale_view.xml',
'stock_view.xml',
],
'installable': True,
}

View File

@@ -18,4 +18,8 @@
</field>
</record>
<!-- In most companies, Incoterms are managed by sales people, not by stock guys
So I give access to Incoterms to Sales Manager : update ACL and add menu -->
<menuitem id="stock_incoterms_sale_config_menu" action="stock.action_incoterms_tree" parent="delivery.sale_menu_delivery" sequence="100"/>
</odoo>

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class SaleReport(models.Model):
_inherit = 'sale.report'
carrier_id = fields.Many2one(
"delivery.carrier", string="Delivery Method", readonly=True)
def _select(self):
select_str = super(SaleReport, self)._select()
select_str += ", s.carrier_id as carrier_id"
return select_str
def _group_by(self):
groupby_str = super(SaleReport, self)._group_by()
groupby_str += ", s.carrier_id"
return groupby_str

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_incoterms_sale_manager,Full access on incoterms to sale manager,stock.model_stock_incoterms,sales_team.group_sale_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_incoterms_sale_manager Full access on incoterms to sale manager stock.model_stock_incoterms sales_team.group_sale_manager 1 1 1 1

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