diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 48082451f35..8e79376aa87 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -61,7 +61,7 @@ class account_bank_statement(osv.osv): return res def _get_period(self, cr, uid, context=None): - periods = self.pool.get('account.period').find(cr, uid) + periods = self.pool.get('account.period').find(cr, uid,context=context) if periods: return periods[0] return False diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index 17fa05d5f24..c1f30824461 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -78,7 +78,7 @@ class account_cash_statement(osv.osv): """ res = {} for statement in self.browse(cr, uid, ids, context=context): - if statement.journal_id.type not in ('cash',): + if (statement.journal_id.type not in ('cash',)) or (not statement.journal_id.cash_control): continue start = end = 0 for line in statement.details_ids: @@ -289,13 +289,13 @@ class account_cash_statement(osv.osv): super(account_cash_statement, self).button_confirm_bank(cr, uid, ids, context=context) absl_proxy = self.pool.get('account.bank.statement.line') - TABLES = (('Profit', 'profit_account_id'), ('Loss', 'loss_account_id'),) + TABLES = ((_('Profit'), 'profit_account_id'), (_('Loss'), 'loss_account_id'),) for obj in self.browse(cr, uid, ids, context=context): if obj.difference == 0.0: continue - for item_label, item_account in TALBES: + for item_label, item_account in TABLES: if getattr(obj.journal_id, item_account): raise osv.except_osv(_('Error!'), _('There is no %s Account on the journal %s.') % (item_label, obj.journal_id.name,)) diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index d9e95f15079..19128b42d6e 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -509,8 +509,8 @@ - - + + @@ -1230,8 +1230,8 @@ - - + + diff --git a/addons/account/i18n/fr.po b/addons/account/i18n/fr.po index f4823e4b626..5a013ceacb9 100644 --- a/addons/account/i18n/fr.po +++ b/addons/account/i18n/fr.po @@ -7,14 +7,15 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2012-06-20 16:18+0000\n" -"Last-Translator: Raphael Collet (OpenERP) \n" +"PO-Revision-Date: 2012-10-30 15:48+0000\n" +"Last-Translator: Frederic Clementi - Camptocamp.com " +"\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-10-30 05:01+0000\n" -"X-Generator: Launchpad (build 16206)\n" +"X-Launchpad-Export-Date: 2012-11-03 05:03+0000\n" +"X-Generator: Launchpad (build 16218)\n" #. module: account #: code:addons/account/account_move_line.py:1215 @@ -151,7 +152,7 @@ msgstr "Définition des enfants" #: code:addons/account/account_bank_statement.py:302 #, python-format msgid "Journal item \"%s\" is not valid." -msgstr "Écriture \"%s\" n'est pas équilibrée" +msgstr "" #. module: account #: model:ir.model,name:account.model_report_aged_receivable @@ -1608,10 +1609,9 @@ msgid "" "entry per accounting document: invoice, refund, supplier payment, bank " "statements, etc." msgstr "" -"Une pièce comptable est composée de plusieurs écritures comptables, chacune " -"étant soit un débit soit un crédit. OpenERP crée automatiquement une pièce " -"comptable par document comptable : facture, avoir, paiement de fournisseur, " -"relevés bancaires, etc." +"Un écriture est composée de plusieurs lignes, chacune étant soit un débit " +"soit un crédit. OpenERP crée automatiquement une écriture par document " +"comptable : facture, avoir, paiement de fournisseur, relevés bancaires, etc." #. module: account #: view:account.entries.report:0 @@ -1703,7 +1703,7 @@ msgstr "Recherche d'un relevé bancaire" #. module: account #: view:account.move.line:0 msgid "Unposted Journal Items" -msgstr "Pièces non comptabiliisées" +msgstr "Ecritures brouillon" #. module: account #: view:account.chart.template:0 @@ -2478,8 +2478,6 @@ msgid "" "In order to delete a bank statement, you must first cancel it to delete " "related journal items." msgstr "" -"Pour supprimer un relevé bancaire vous devez préalablement supprimer les " -"lignes rapprochées." #. module: account #: field:account.invoice,payment_term:0 @@ -3629,7 +3627,7 @@ msgstr "État des ventes par type de compte" #. module: account #: view:account.move.line:0 msgid "Unreconciled Journal Items" -msgstr "Ecritures non lettrées" +msgstr "Ecritures non léttrées" #. module: account #: sql_constraint:res.currency:0 @@ -3900,7 +3898,7 @@ msgstr "" #. module: account #: model:ir.actions.report.xml,name:account.account_account_balance_landscape msgid "Account balance" -msgstr "Balance des comptes" +msgstr "Balance générale" #. module: account #: report:account.invoice:0 @@ -3972,7 +3970,7 @@ msgstr "Nom" #. module: account #: model:ir.model,name:account.model_account_aged_trial_balance msgid "Account Aged Trial balance Report" -msgstr "Rapport du solde de la balance agée" +msgstr "Balance agée" #. module: account #: code:addons/account/account_move_line.py:582 @@ -4181,7 +4179,7 @@ msgstr "" #. module: account #: view:account.move.line:0 msgid "Posted Journal Items" -msgstr "Ecritures saisies" +msgstr "Ecritures validées" #. module: account #: view:account.tax.template:0 @@ -4244,7 +4242,7 @@ msgstr "Titulaire du compte bancaire" #: model:ir.actions.report.xml,name:account.account_account_balance #: model:ir.ui.menu,name:account.menu_general_Balance_report msgid "Trial Balance" -msgstr "Balance des comptes" +msgstr "Balance générale" #. module: account #: model:ir.model,name:account.model_account_invoice_cancel @@ -4659,7 +4657,7 @@ msgstr "Sert de compte par défaut pour le crédit" #: view:validate.account.move:0 #: view:validate.account.move.lines:0 msgid "Post Journal Entries" -msgstr "Comptabiliser les pièces comptables dans le journal" +msgstr "Valider les écritures" #. module: account #: selection:account.invoice,state:0 @@ -4927,7 +4925,7 @@ msgstr "" #: code:addons/account/account.py:923 #, python-format msgid "Opening Period" -msgstr "Période ouverte" +msgstr "Période d'ouverture" #. module: account #: view:account.move:0 @@ -5611,7 +5609,7 @@ msgstr "Quantité de produits" #: selection:account.move,state:0 #: view:account.move.line:0 msgid "Unposted" -msgstr "Non comptabilisé" +msgstr "Non validée" #. module: account #: view:account.change.currency:0 @@ -5823,7 +5821,7 @@ msgstr "Journal d'écriture" #: model:ir.actions.act_window,name:account.action_account_fiscalyear_close #: model:ir.ui.menu,name:account.menu_wizard_fy_close msgid "Generate Opening Entries" -msgstr "Générer les écritures d'ouverture" +msgstr "Générer l'écriture d'ouverture" #. module: account #: code:addons/account/account_move_line.py:775 @@ -7117,7 +7115,7 @@ msgstr "Sélection du journal" #: code:addons/account/account.py:432 #, python-format msgid "Opening Balance" -msgstr "Solde initial" +msgstr "" #. module: account #: model:ir.model,name:account.model_account_move_reconcile @@ -7727,8 +7725,7 @@ msgstr "Signes sur les Rapports" #: code:addons/account/wizard/account_fiscalyear_close.py:88 #, python-format msgid "The periods to generate opening entries were not found" -msgstr "" -"Les périodes pour générer les écritures d'ouverture n'ont pas été trouvées." +msgstr "Aucune période d'ouverture trouvée" #. module: account #: model:account.account.type,name:account.data_account_type_view @@ -7822,7 +7819,7 @@ msgstr "Journal des ventes" #: code:addons/account/wizard/account_move_journal.py:104 #, python-format msgid "Open Journal Items !" -msgstr "Écritures ouvertes !" +msgstr "" #. module: account #: model:ir.model,name:account.model_account_invoice_tax @@ -7859,7 +7856,7 @@ msgstr "" #. module: account #: view:account.move:0 msgid "Unposted Journal Entries" -msgstr "Pièces non comptabilisées" +msgstr "Ecritures non validées" #. module: account #: view:product.product:0 @@ -7976,7 +7973,7 @@ msgstr "Code de la case" #. module: account #: view:validate.account.move:0 msgid "Post Journal Entries of a Journal" -msgstr "Comptabiliser les pièces comptables d'un journal" +msgstr "Valider les écritures" #. module: account #: view:product.product:0 @@ -8161,7 +8158,7 @@ msgstr "Ligne de caisse" #: model:ir.actions.report.xml,name:account.account_3rdparty_ledger_other #: model:ir.ui.menu,name:account.menu_account_partner_ledger msgid "Partner Ledger" -msgstr "Grand Livre" +msgstr "Livre des tiers" #. module: account #: selection:account.tax.template,type:0 @@ -8359,7 +8356,7 @@ msgstr "" #: help:account.account,reconcile:0 msgid "" "Check this box if this account allows reconciliation of journal items." -msgstr "Cochez si le compte autorise le lettrage des pièces." +msgstr "Cochez si le compte autorise le lettrage des écritures." #. module: account #: help:account.period,state:0 @@ -9220,7 +9217,7 @@ msgstr "inconnu" #: code:addons/account/account.py:3130 #, python-format msgid "Opening Entries Journal" -msgstr "Journal des écritures d'ouverture" +msgstr "Journal d'ouverture" #. module: account #: model:process.transition,note:account.process_transition_customerinvoice0 @@ -10006,7 +10003,7 @@ msgstr "Ce compte de tiers remplacera le compte par défaut." #. module: account #: field:account.period,special:0 msgid "Opening/Closing Period" -msgstr "Ouverture/clôture d'exercice" +msgstr "Période d'ouverture/clôture" #. module: account #: field:account.account,currency_id:0 @@ -10336,7 +10333,7 @@ msgstr "Date de fin" #: model:ir.actions.act_window,name:account.action_account_open_closed_fiscalyear #: model:ir.ui.menu,name:account.menu_wizard_account_open_closed_fiscalyear msgid "Cancel Opening Entries" -msgstr "Annuler les écritures d'ouvertures" +msgstr "Annuler l'écriture d'ouverture" #. module: account #: field:account.payment.term.line,days2:0 @@ -10505,7 +10502,7 @@ msgstr "Signer pour le parent" #. module: account #: model:ir.model,name:account.model_account_balance_report msgid "Trial Balance Report" -msgstr "Rapport sur la balance des comptes" +msgstr "Balance générale" #. module: account #: model:ir.actions.act_window,name:account.action_bank_statement_draft_tree @@ -10688,7 +10685,7 @@ msgstr "Intervalle" #. module: account #: view:account.analytic.line:0 msgid "Analytic Journal Items related to a purchase journal." -msgstr "Imputations analytiques relatives au journal des achats." +msgstr "Ecritures analytiques relatives au journal des achats." #. module: account #: help:account.account,type:0 @@ -11213,7 +11210,7 @@ msgstr "Future" #. module: account #: view:account.move.line:0 msgid "Search Journal Items" -msgstr "Rechercher des écritures comptables" +msgstr "Recherche par ligne d'écritures" #. module: account #: help:account.tax,base_sign:0 diff --git a/addons/account/i18n/ru.po b/addons/account/i18n/ru.po index d2e8f8ae7cb..f07753022d5 100644 --- a/addons/account/i18n/ru.po +++ b/addons/account/i18n/ru.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2012-10-26 09:03+0000\n" +"PO-Revision-Date: 2012-10-31 10:41+0000\n" "Last-Translator: Chertykov Denis \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-10-30 05:04+0000\n" -"X-Generator: Launchpad (build 16206)\n" +"X-Launchpad-Export-Date: 2012-11-03 05:03+0000\n" +"X-Generator: Launchpad (build 16218)\n" #. module: account #: view:account.invoice.report:0 @@ -7939,7 +7939,7 @@ msgstr "Строка кассы" #: model:ir.actions.report.xml,name:account.account_3rdparty_ledger_other #: model:ir.ui.menu,name:account.menu_account_partner_ledger msgid "Partner Ledger" -msgstr "Книгапартнера" +msgstr "Книга партнера" #. module: account #: selection:account.tax.template,type:0 diff --git a/addons/account/i18n/sl.po b/addons/account/i18n/sl.po index 7557091bd4e..6748e5db3b1 100644 --- a/addons/account/i18n/sl.po +++ b/addons/account/i18n/sl.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2012-06-20 16:03+0000\n" -"Last-Translator: Raphael Collet (OpenERP) \n" +"PO-Revision-Date: 2012-11-01 18:30+0000\n" +"Last-Translator: Dusan Laznik \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-10-30 05:04+0000\n" -"X-Generator: Launchpad (build 16206)\n" +"X-Launchpad-Export-Date: 2012-11-03 05:04+0000\n" +"X-Generator: Launchpad (build 16218)\n" #. module: account #: view:account.invoice.report:0 @@ -1587,7 +1587,7 @@ msgstr "Neobdavčeno" #. module: account #: view:account.partner.reconcile.process:0 msgid "Go to Next Partner" -msgstr "" +msgstr "Naslednji parner" #. module: account #: view:account.bank.statement:0 @@ -3150,7 +3150,7 @@ msgstr "Predloge kontnih načrtov" #. module: account #: model:ir.actions.act_window,name:account.action_wizard_multi_chart msgid "Set Your Accounting Options" -msgstr "" +msgstr "Nastavitve" #. module: account #: view:report.account.sales:0 @@ -4358,7 +4358,7 @@ msgstr "res_config_contents" #. module: account #: view:account.unreconcile:0 msgid "Unreconciliate Transactions" -msgstr "" +msgstr "Neusklajene postavke" #. module: account #: help:account.chart.template,visible:0 @@ -4558,7 +4558,7 @@ msgstr "Datum dogodka" #. module: account #: view:account.unreconcile.reconcile:0 msgid "Unreconciliation Transactions" -msgstr "" +msgstr "Naknadno odprte postavke" #. module: account #: field:account.tax,ref_tax_code_id:0 @@ -6046,7 +6046,7 @@ msgstr "Davek(%)" #. module: account #: view:account.addtmpl.wizard:0 msgid "Create an Account Based on this Template" -msgstr "" +msgstr "Ustvarite konto na osnovi te predloge" #. module: account #: view:account.account.type:0 @@ -8827,7 +8827,7 @@ msgstr "Prodaja po vrstah" #. module: account #: view:account.analytic.cost.ledger.journal.report:0 msgid "Cost Ledger for Period" -msgstr "" +msgstr "Stroški za obdobje" #. module: account #: help:account.tax,child_depend:0 @@ -10307,7 +10307,7 @@ msgstr "Urejanje vrst dnevnikov." #. module: account #: view:account.payment.term:0 msgid "Description on Invoices" -msgstr "" +msgstr "Opis na računih" #. module: account #: model:ir.model,name:account.model_account_analytic_chart diff --git a/addons/account/i18n/zh_CN.po b/addons/account/i18n/zh_CN.po index 3ad8b790323..d722cd5673b 100644 --- a/addons/account/i18n/zh_CN.po +++ b/addons/account/i18n/zh_CN.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2012-10-25 16:26+0000\n" -"Last-Translator: AllanWong <18895563@qq.com>\n" +"PO-Revision-Date: 2012-11-01 08:44+0000\n" +"Last-Translator: ccdos \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-10-30 05:07+0000\n" -"X-Generator: Launchpad (build 16206)\n" +"X-Launchpad-Export-Date: 2012-11-03 05:04+0000\n" +"X-Generator: Launchpad (build 16218)\n" #. module: account #: view:account.invoice.report:0 @@ -2330,7 +2330,7 @@ msgstr "筛选" #: selection:report.invoice.created,state:0 #, python-format msgid "Open" -msgstr "打开" +msgstr "待支付" #. module: account #: model:process.node,note:account.process_node_draftinvoices0 @@ -4668,7 +4668,7 @@ msgstr "税适用" #: model:ir.ui.menu,name:account.menu_eaction_account_moves_sale #, python-format msgid "Journal Items" -msgstr "会计凭证行" +msgstr "会计凭证明细" #. module: account #: code:addons/account/account.py:1095 @@ -9642,7 +9642,7 @@ msgstr "本报表让您打印或产生一个由所有账簿生成的总账" #: selection:account.account.template,type:0 #: selection:account.entries.report,type:0 msgid "Regular" -msgstr "定期" +msgstr "常规科目" #. module: account #: view:account.account:0 @@ -10557,7 +10557,7 @@ msgstr "前景" #. module: account #: view:account.move.line:0 msgid "Search Journal Items" -msgstr "搜索分录" +msgstr "搜索会计凭证明细" #. module: account #: help:account.tax,base_sign:0 diff --git a/addons/account/partner.py b/addons/account/partner.py index a9b2b99588d..3355363308b 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -209,7 +209,7 @@ class res_partner(osv.osv): relation='account.fiscal.position', string="Fiscal Position", view_load=True, - help="The fiscal position will determine taxes and the accounts used for the partner.", + help="The fiscal position will determine taxes and accounts used for the partner.", ), 'property_payment_term': fields.property( 'account.payment.term', diff --git a/addons/account/product.py b/addons/account/product.py index 9be49b4c56a..0a6a0eda10c 100644 --- a/addons/account/product.py +++ b/addons/account/product.py @@ -30,14 +30,14 @@ class product_category(osv.osv): relation='account.account', string="Income Account", view_load=True, - help="This account will be used for invoices to value sales for the current product category"), + help="This account will be used for invoices to value sales."), 'property_account_expense_categ': fields.property( - 'account.account', + 'account.account', type='many2one', relation='account.account', string="Expense Account", view_load=True, - help="This account will be used for invoices to value expenses for the current product category"), + help="This account will be used for invoices to value expenses."), } product_category() @@ -60,14 +60,14 @@ class product_template(osv.osv): relation='account.account', string="Income Account", view_load=True, - help="This account will be used for invoices instead of the default one to value sales for the current product"), + help="This account will be used for invoices instead of the default one to value sales for the current product."), 'property_account_expense': fields.property( 'account.account', type='many2one', relation='account.account', string="Expense Account", view_load=True, - help="This account will be used for invoices instead of the default one to value expenses for the current product"), + help="This account will be used for invoices instead of the default one to value expenses for the current product."), } product_template() diff --git a/addons/account/product_view.xml b/addons/account/product_view.xml index 4d64af3e154..9b54647e726 100644 --- a/addons/account/product_view.xml +++ b/addons/account/product_view.xml @@ -49,9 +49,11 @@ - - - + + + + + diff --git a/addons/account/project/analytic_account_demo.xml b/addons/account/project/analytic_account_demo.xml index b53b76969ab..2c4acc6bc45 100644 --- a/addons/account/project/analytic_account_demo.xml +++ b/addons/account/project/analytic_account_demo.xml @@ -94,8 +94,8 @@ open - - Magasin BML 1 + + Millennium Industries normal @@ -121,8 +121,8 @@ - - DistriPC + + Delta PC normal @@ -145,8 +145,8 @@ open - - Leclerc + + Luminous Technologies normal @@ -161,8 +161,8 @@ - - OpenERP SA AT Work + + Think Big Systems normal diff --git a/addons/account/project/project_view.xml b/addons/account/project/project_view.xml index d0813edead8..88cf901fdae 100644 --- a/addons/account/project/project_view.xml +++ b/addons/account/project/project_view.xml @@ -35,7 +35,6 @@ - @@ -144,8 +143,8 @@ diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml index e352a33d850..289db376e5d 100644 --- a/addons/account/report/account_invoice_report_view.xml +++ b/addons/account/report/account_invoice_report_view.xml @@ -42,7 +42,7 @@ - + diff --git a/addons/account/report/account_print_overdue.rml b/addons/account/report/account_print_overdue.rml index 3e8b9b04b62..dd573580c6c 100644 --- a/addons/account/report/account_print_overdue.rml +++ b/addons/account/report/account_print_overdue.rml @@ -277,7 +277,7 @@
- [[ getLines(o) and removeParentNode('section')]]There is nothing due with this customer + [[ getLines(o) and removeParentNode('section')]]There is nothing due with this customer.
diff --git a/addons/account/res_config.py b/addons/account/res_config.py index 41121db20f7..8f525795d1c 100644 --- a/addons/account/res_config.py +++ b/addons/account/res_config.py @@ -262,6 +262,7 @@ class account_config_settings(osv.osv_memory): 'sale_tax_rate': config.sale_tax_rate, 'purchase_tax_rate': config.purchase_tax_rate, 'complete_tax_set': config.complete_tax_set, + 'currency_id': config.currency_id.id, }, context) wizard.execute(cr, uid, [wizard_id], context) diff --git a/addons/account/res_config_view.xml b/addons/account/res_config_view.xml index d4bafca41be..52c8c8daaa9 100644 --- a/addons/account/res_config_view.xml +++ b/addons/account/res_config_view.xml @@ -268,7 +268,7 @@ inline
- diff --git a/addons/account_accountant/account_accountant_data.xml b/addons/account_accountant/account_accountant_data.xml index e02db45fcd8..cf143b1bb58 100644 --- a/addons/account_accountant/account_accountant_data.xml +++ b/addons/account_accountant/account_accountant_data.xml @@ -5,6 +5,7 @@ + diff --git a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml index d56b6ee4760..9eb8fe90895 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml @@ -3,13 +3,21 @@ - Time & Costs to Invoice + Time & Materials to Invoice account.analytic.line form tree,form [('invoice_id','=',False)] {'search_default_to_invoice': 1} + +

+ You will find here timesheets and purchases you did for + contracts that can be reinvoiced to the customer. If you want + to record new activities to invoice, you should use the timesheet + menu instead. +

+
diff --git a/addons/account_analytic_default/__openerp__.py b/addons/account_analytic_default/__openerp__.py index 213d055f8de..0af606821ce 100644 --- a/addons/account_analytic_default/__openerp__.py +++ b/addons/account_analytic_default/__openerp__.py @@ -38,7 +38,7 @@ Allows to automatically select analytic accounts based on criterions: 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', 'images': ['images/analytic_defaults.jpeg'], - 'depends': ['sale'], + 'depends': ['sale_stock'], 'data': [ 'security/ir.model.access.csv', 'security/account_analytic_default_security.xml', diff --git a/addons/account_anglo_saxon/product.py b/addons/account_anglo_saxon/product.py index a2a5f7e6e95..87ca24753d6 100644 --- a/addons/account_anglo_saxon/product.py +++ b/addons/account_anglo_saxon/product.py @@ -38,14 +38,14 @@ class product_category(osv.osv): relation='account.account', string="Income Account", view_load=True, - help="This account will be used to value outgoing stock for the current product category using sale price"), + help="This account will be used to value outgoing stock using sale price."), 'property_account_expense_categ': fields.property( 'account.account', type='many2one', relation='account.account', string="Expense Account", view_load=True, - help="This account will be used to value outgoing stock for the current product category using cost price"), + help="This account will be used to value outgoing stock using cost price."), } product_category() @@ -68,14 +68,14 @@ class product_template(osv.osv): relation='account.account', string="Income Account", view_load=True, - help="This account will be used to value outgoing stock for the current product category using sale price"), + help="This account will be used to value outgoing stock using sale price."), 'property_account_expense': fields.property( 'account.account', type='many2one', relation='account.account', string="Expense Account", view_load=True, - help="This account will be used to value outgoing stock for the current product category using cost price"), + help="This account will be used to value outgoing stock using cost price."), } product_template() diff --git a/addons/account_anglo_saxon/product_view.xml b/addons/account_anglo_saxon/product_view.xml index 4acc69b7595..43029616c5b 100644 --- a/addons/account_anglo_saxon/product_view.xml +++ b/addons/account_anglo_saxon/product_view.xml @@ -29,10 +29,10 @@ product.category.property.form.inherit.stock product.category - + - + diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index 585798a5235..ac9024fd51d 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -134,10 +134,11 @@ class account_asset_asset(osv.osv): def compute_depreciation_board(self, cr, uid, ids, context=None): depreciation_lin_obj = self.pool.get('account.asset.depreciation.line') + currency_obj = self.pool.get('res.currency') for asset in self.browse(cr, uid, ids, context=context): if asset.value_residual == 0.0: continue - posted_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_check', '=', True)]) + posted_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_check', '=', True)],order='depreciation_date desc') old_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_id', '=', False)]) if old_depreciation_line_ids: depreciation_lin_obj.unlink(cr, uid, old_depreciation_line_ids, context=context) @@ -148,7 +149,12 @@ class account_asset_asset(osv.osv): else: # depreciation_date = 1st January of purchase year purchase_date = datetime.strptime(asset.purchase_date, '%Y-%m-%d') - depreciation_date = datetime(purchase_date.year, 1, 1) + #if we already have some previous validated entries, starting date isn't 1st January but last entry + method period + if (len(posted_depreciation_line_ids)>0): + last_depreciation_date = datetime.strptime(depreciation_lin_obj.browse(cr,uid,posted_depreciation_line_ids[0],context=context).depreciation_date, '%Y-%m-%d') + depreciation_date = (last_depreciation_date+relativedelta(months=+asset.method_period)) + else: + depreciation_date = datetime(purchase_date.year, 1, 1) day = depreciation_date.day month = depreciation_date.month year = depreciation_date.year @@ -158,6 +164,10 @@ class account_asset_asset(osv.osv): for x in range(len(posted_depreciation_line_ids), undone_dotation_number): i = x + 1 amount = self._compute_board_amount(cr, uid, asset, i, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date, context=context) + company_currency = asset.company_id.currency_id.id + current_currency = asset.currency_id.id + # compute amount into company currency + amount = currency_obj.compute(cr, uid, current_currency, company_currency, amount, context=context) residual_amount -= amount vals = { 'amount': amount, @@ -191,7 +201,7 @@ class account_asset_asset(osv.osv): def _amount_residual(self, cr, uid, ids, name, args, context=None): cr.execute("""SELECT - l.asset_id as id, round(SUM(abs(l.debit-l.credit))) AS amount + l.asset_id as id, SUM(abs(l.debit-l.credit)) AS amount FROM account_move_line l WHERE @@ -349,8 +359,8 @@ class account_asset_depreciation_line(osv.osv): 'sequence': fields.integer('Sequence', required=True), 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True), 'parent_state': fields.related('asset_id', 'state', type='char', string='State of Asset'), - 'amount': fields.float('Depreciation Amount', required=True), - 'remaining_value': fields.float('Amount to Depreciate', required=True), + 'amount': fields.float('Depreciation Amount', digits_compute=dp.get_precision('Account'), required=True), + 'remaining_value': fields.float('Amount to Depreciate', digits_compute=dp.get_precision('Account'),required=True), 'depreciated_value': fields.float('Amount Already Depreciated', required=True), 'depreciation_date': fields.date('Depreciation Date', select=1), 'move_id': fields.many2one('account.move', 'Depreciation Entry'), diff --git a/addons/account_bank_statement_extensions/account_bank_statement.py b/addons/account_bank_statement_extensions/account_bank_statement.py index 6eeda843dc5..3d097d3c757 100644 --- a/addons/account_bank_statement_extensions/account_bank_statement.py +++ b/addons/account_bank_statement_extensions/account_bank_statement.py @@ -105,8 +105,7 @@ account_bank_statement_line_global() class account_bank_statement_line(osv.osv): _inherit = 'account.bank.statement.line' _columns = { - 'date': fields.date('Entry Date', required=True, states={'confirm': [('readonly', True)]}), - 'val_date': fields.date('Valuta Date', states={'confirm': [('readonly', True)]}), + 'val_date': fields.date('Value Date', states={'confirm': [('readonly', True)]}), 'globalisation_id': fields.many2one('account.bank.statement.line.global', 'Globalisation ID', states={'confirm': [('readonly', True)]}, help="Code to identify transactions belonging to the same globalisation level within a batch payment"), diff --git a/addons/account_bank_statement_extensions/account_bank_statement_view.xml b/addons/account_bank_statement_extensions/account_bank_statement_view.xml index 9164c00373f..3d5560856a3 100644 --- a/addons/account_bank_statement_extensions/account_bank_statement_view.xml +++ b/addons/account_bank_statement_extensions/account_bank_statement_view.xml @@ -148,7 +148,7 @@ Bank Statement Lines account.bank.statement.line form - tree,graph,form + tree,form {'block_statement_line_delete' : 1} diff --git a/addons/account_budget/account_budget_view.xml b/addons/account_budget/account_budget_view.xml index fdc358f5c98..cb0466a1c92 100644 --- a/addons/account_budget/account_budget_view.xml +++ b/addons/account_budget/account_budget_view.xml @@ -284,13 +284,13 @@ - account.analytic.account.form.inherot.budget + account.analytic.account.form.inherit.budget account.analytic.account - + @@ -310,11 +310,6 @@ - - - - - diff --git a/addons/account_payment/i18n/zh_CN.po b/addons/account_payment/i18n/zh_CN.po index eabd2b65207..20cb178fe73 100644 --- a/addons/account_payment/i18n/zh_CN.po +++ b/addons/account_payment/i18n/zh_CN.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2012-02-10 15:03+0000\n" -"Last-Translator: Wei \"oldrev\" Li \n" +"PO-Revision-Date: 2012-10-30 15:45+0000\n" +"Last-Translator: ccdos \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-10-30 05:13+0000\n" -"X-Generator: Launchpad (build 16206)\n" +"X-Launchpad-Export-Date: 2012-11-03 05:04+0000\n" +"X-Generator: Launchpad (build 16218)\n" #. module: account_payment #: field:payment.order,date_scheduled:0 @@ -653,7 +653,7 @@ msgstr "公司币别" #: view:payment.line:0 #: view:payment.order:0 msgid "Payment" -msgstr "付款" +msgstr "支付" #. module: account_payment #: report:payment.order:0 diff --git a/addons/account_voucher/__openerp__.py b/addons/account_voucher/__openerp__.py index c0011c8b3c5..927fda19182 100644 --- a/addons/account_voucher/__openerp__.py +++ b/addons/account_voucher/__openerp__.py @@ -61,6 +61,7 @@ This module manages: 'account_voucher_data.xml', ], 'test' : [ + 'test/case5_suppl_usd_usd.yml', 'test/account_voucher.yml', 'test/sales_receipt.yml', 'test/sales_payment.yml', diff --git a/addons/account_voucher/test/case5_suppl_usd_usd.yml b/addons/account_voucher/test/case5_suppl_usd_usd.yml new file mode 100644 index 00000000000..3cae9667d84 --- /dev/null +++ b/addons/account_voucher/test/case5_suppl_usd_usd.yml @@ -0,0 +1,185 @@ +- + In order to check the Account_voucher module with multi-currency in OpenERP, + I create a supplier invoices in USD and make the payment in USD but with another exchange rate and with a write-off +- + I create a cash account with currency USD +- + !record {model: account.account, id: account_cash_usd_id}: + currency_id: base.USD + name: "cash account in usd" + code: "Xcash usd" + type: 'liquidity' + user_type: "account.data_account_type_cash" + +- + I create currency USD in OpenERP for November of 1.8 Rate +- + !record {model: res.currency.rate, id: nov_usd}: + currency_id: base.USD + name: !eval "'%s-11-01' %(datetime.now().year)" + rate: 1.8 +- + I create currency USD in OpenERP for December of 1.5 Rate +- + !record {model: res.currency.rate, id: dec_usd}: + currency_id: base.USD + name: !eval "'%s-12-01' %(datetime.now().year)" + rate: 1.5 +- + I set the income and expense currency accounts on the main company +- + !python {model: res.company}: | + from datetime import datetime + vals = { + 'income_currency_exchange_account_id': ref('account.o_expense'), + 'expense_currency_exchange_account_id': ref('account.o_expense')} + self.write(cr, uid, ref('base.main_company'), vals) +- + I create a bank journal with EUR as currency +- + !record {model: account.journal, id: bank_journal_EUR}: + name: Bank Journal(EUR) + code: BEUR + type: bank + analytic_journal_id: account.sit + sequence_id: account.sequence_bank_journal + default_debit_account_id: account.cash + default_credit_account_id: account.cash + company_id: base.main_company + view_id: account.account_journal_bank_view +- + I create a bank journal with USD as currency +- + !record {model: account.journal, id: bank_journal_USD}: + name: Bank Journal(USD) + code: BUSD + type: bank + analytic_journal_id: account.sit + sequence_id: account.sequence_bank_journal + default_debit_account_id: account_cash_usd_id + default_credit_account_id: account_cash_usd_id + currency: base.USD + company_id: base.main_company + view_id: account.account_journal_bank_view +- + I set the context as would do the action in supplier invoice menuitem +- + !context + 'default_type': 'in_invoice' +- + I create the invoice on 1st november for 1000 USD +- + !record {model: account.invoice, id: account_supplier_invoice_november, view: account.invoice_supplier_form}: + account_id: account.a_pay + company_id: base.main_company + currency_id: base.USD + date_invoice: !eval "'%s-11-01' %(datetime.now().year)" + period_id: account.period_1 + invoice_line: + - account_id: account.a_expense + name: '[PCSC234] PC Assemble SC234' + price_unit: 1000.0 + quantity: 1.0 + product_id: product.product_product_3 + uos_id: product.product_uom_unit + journal_id: account.sales_journal + partner_id: base.res_partner_19 + reference_type: none + check_total : 1000 +- + I Validate invoice by clicking on Validate button +- + !workflow {model: account.invoice, action: invoice_open, ref: account_supplier_invoice_november} +- + I check that first invoice move is correct for creditor account(debit - credit == -555.56) +- + !python {model: account.invoice}: | + invoice_id = self.browse(cr, uid, ref("account_supplier_invoice_november")) + assert invoice_id.move_id, "Move not created for open invoice" + move_line_obj = self.pool.get('account.move.line') + move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)]) + move_line = move_line_obj.browse(cr, uid, move_lines[0]) + assert (move_line.debit - move_line.credit == -555.56), "Invoice move is incorrect for creditor account" + assert (move_line.amount_currency == -1000), "Amount currency is incorrect for creditor account" +- + I set the context that will be used for the encoding of all the vouchers of this file +- + !context + 'type': 'payment' +- + I create the voucher of payment with values $950, journal USD +- + !record {model: account.voucher, id: account_voucher_case_5_supplier_flow, view: view_vendor_payment_form}: + account_id: account.cash + amount: 950.0 + company_id: base.main_company + journal_id: bank_journal_USD + name: 'Payment: Case Basic SUPPLIER USD/USD' + partner_id: base.res_partner_19 + period_id: account.period_12 + date: !eval time.strftime("%Y-12-01") + payment_option: 'with_writeoff' + writeoff_acc_id: account.a_expense + comment: 'Write Off' + type: 'payment' +- + I fill $1000 for the previously encoded invoice +- + !python {model: account.voucher}: | + import netsvc, time + vals = {} + voucher_id = self.browse(cr, uid, ref('account_voucher_case_5_supplier_flow')) + for item in voucher_id.line_dr_ids: + if item.amount_unreconciled == 1000.00: + self.pool.get('account.voucher.line').write(cr, uid, [item.id], {'amount': 1000}) + assert (voucher_id.state=='draft'), "Voucher is not in draft state" +- + I check that writeoff amount computed is -50.0 +- + !assert {model: account.voucher, id: account_voucher_case_5_supplier_flow}: + - writeoff_amount == -50.0 +- + I confirm the voucher +- + !workflow {model: account.voucher, action: proforma_voucher, ref: account_voucher_case_5_supplier_flow} +- + I check that the move of my voucher is valid and that it is posted +- + !python {model: account.voucher}: | + voucher_id = self.browse(cr, uid, ref('account_voucher_case_5_supplier_flow')) + assert voucher_id.state == 'posted', "Voucher state is not posted" + for move_line in voucher_id.move_id.line_id: + assert move_line.state == 'valid', "Voucher move is not valid" +- + I check that my creditor account is correct +- + I check that the bank entry is -$950 and 633.33€ as amount_currency and credit, respectively. +- + I check that the creditor account has 1 new line with $1000 and 666.67€ as amount_currency and debit, respectively. +- + I check that my currency rate difference is correct (111.11€ in debit/credit with no amount_currency) +- + I check that my writeoff is correct. 33.34€ in credit with -$50 as amount currency +- + !python {model: account.voucher}: | + voucher_id = self.browse(cr, uid, ref('account_voucher_case_5_supplier_flow')) + for move_line in voucher_id.move_id.line_id: + if move_line.amount_currency == -950.00: + assert move_line.credit == 633.33, "Wrong bank entry." + elif move_line.credit == 111.11 or move_line.debit == 111.11: + assert move_line.amount_currency == 0.00, "Incorrect Currency Difference." + elif move_line.credit == 33.34: + assert move_line.amount_currency == -50.0, "Writeoff amount is wrong." + elif move_line.debit == 666.67: + assert move_line.amount_currency == 1000.0, "Wrong supplier entry." + else: + assert False, "Wrong entry. Unrecognized account move line" +- + I check the residual amount of invoice, it should be 0 in residual currency and 0 in amount_residual and paid +- + !python {model: account.invoice}: | + invoice_id = self.browse(cr, uid, ref("account_supplier_invoice_november")) + move_line_obj = self.pool.get('account.move.line') + move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)]) + move_line = move_line_obj.browse(cr, uid, move_lines[0]) + assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for supplier invoice" diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml index 6666ac77d2f..881a5e38f1c 100644 --- a/addons/account_voucher/voucher_payment_receipt_view.xml +++ b/addons/account_voucher/voucher_payment_receipt_view.xml @@ -290,7 +290,7 @@ - + %s has been created.") % (obj.partner_id.name), + message = _("Contract created.") + if obj.partner_id: + message = _("Contract for %s has been created.") % (obj.partner_id.name,) + self.message_post(cr, uid, [obj.id], body=message, subtype="analytic.mt_account_status", context=context) account_analytic_account() diff --git a/addons/analytic/analytic_view.xml b/addons/analytic/analytic_view.xml index 2c6136f2629..17371f8609f 100644 --- a/addons/analytic/analytic_view.xml +++ b/addons/analytic/analytic_view.xml @@ -23,11 +23,11 @@ - - + + diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 54dcc5b4362..09f9cb23da2 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -20,11 +20,10 @@ ############################################################################## import logging -import werkzeug - import openerp from openerp.modules.registry import RegistryManager -from openerp.addons.web.controllers.main import login_and_redirect + +from ..res_users import SignupError _logger = logging.getLogger(__name__) @@ -41,22 +40,18 @@ class Controller(openerp.addons.web.http.Controller): user_info = res_partner.signup_retrieve_info(cr, openerp.SUPERUSER_ID, token) return user_info - @openerp.addons.web.http.httprequest - def signup(self, req, dbname, token, name, login, password, state=''): - """ sign up a user (new or existing), and log it in """ - url = '/' + @openerp.addons.web.http.jsonrequest + def signup(self, req, dbname, token, name, login, password): + """ sign up a user (new or existing)""" registry = RegistryManager.get(dbname) with registry.cursor() as cr: + res_users = registry.get('res.users') + values = {'name': name, 'login': login, 'password': password} try: - res_users = registry.get('res.users') - values = {'name': name, 'login': login, 'password': password} - credentials = res_users.signup(cr, openerp.SUPERUSER_ID, values, token) - cr.commit() - return login_and_redirect(req, *credentials, redirect_url='/#%s'%state) - except Exception as e: - # signup error - _logger.exception('error when signup') - url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(e.message) - return werkzeug.utils.redirect(url) + res_users.signup(cr, openerp.SUPERUSER_ID, values, token) + except SignupError, e: + return {'error': openerp.tools.exception_to_unicode(e)} + cr.commit() + return {} # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py index 69eee4f18e0..973a8da6588 100644 --- a/addons/auth_signup/res_users.py +++ b/addons/auth_signup/res_users.py @@ -29,6 +29,9 @@ from openerp import SUPERUSER_ID from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT from openerp.tools.safe_eval import safe_eval +class SignupError(Exception): + pass + def random_token(): # the token has an entropy of about 120 bits (6 bits/char * 20 chars) chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' @@ -101,12 +104,12 @@ class res_partner(osv.Model): partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context) if not partner_ids: if raise_exception: - raise Exception("Signup token '%s' is not valid" % token) + raise SignupError("Signup token '%s' is not valid" % token) return False partner = self.browse(cr, uid, partner_ids[0], context) if check_validity and not partner.signup_valid: if raise_exception: - raise Exception("Signup token '%s' is no longer valid" % token) + raise SignupError("Signup token '%s' is no longer valid" % token) return False return partner @@ -194,7 +197,7 @@ class res_users(osv.Model): # check that uninvited users may sign up if 'partner_id' not in values: if not safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')): - raise Exception('Signup is not allowed for uninvited users') + raise SignupError('Signup is not allowed for uninvited users') # create a copy of the template user (attached to a specific partner_id if given) values['active'] = True diff --git a/addons/auth_signup/static/src/js/auth_signup.js b/addons/auth_signup/static/src/js/auth_signup.js index 0974b73292d..bb249429894 100644 --- a/addons/auth_signup/static/src/js/auth_signup.js +++ b/addons/auth_signup/static/src/js/auth_signup.js @@ -8,12 +8,20 @@ openerp.auth_signup = function(instance) { var d = this._super(); // to switch between the signup and regular login form - this.$('a.oe_signup_signup').click(function() { + this.$('a.oe_signup_signup').click(function(ev) { + if (ev) { + ev.preventDefault(); + } self.$el.addClass("oe_login_signup"); + return false; }); - this.$('a.oe_signup_back').click(function() { + this.$('a.oe_signup_back').click(function(ev) { + if (ev) { + ev.preventDefault(); + } self.$el.removeClass("oe_login_signup"); delete self.params.token; + return false; }); // if there is an error message in params, show it then forget it @@ -90,10 +98,19 @@ openerp.auth_signup = function(instance) { name: name, login: login, password: password, - state: $.param(this.params) + //state: $.param(this.params) }; - var url = "/auth_signup/signup?" + $.param(params); - window.location = url; + + var self = this, + super_ = this._super; + this.rpc('/auth_signup/signup', params) + .done(function(result) { + if (result.error) { + self.show_error(result.error); + } else { + super_.apply(self, [ev]); + } + }); } else { // regular login this._super(ev); diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index bb432aa4d61..d225c3f90d9 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -336,7 +336,7 @@ class calendar_attendee(osv.osv): ('non-participant', 'For information Purpose')], 'Role', \ help='Participation role for the calendar user'), 'state': fields.selection([('needs-action', 'Needs Action'), - ('tentative', 'Tentative'), + ('tentative', 'Uncertain'), ('declined', 'Declined'), ('accepted', 'Accepted'), ('delegated', 'Delegated')], 'Status', readonly=True, \ @@ -559,7 +559,8 @@ property or property parameter."), for vals in self.browse(cr, uid, ids, context=context): if vals.ref and vals.ref.user_id: mod_obj = self.pool.get(vals.ref._name) - defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id} + res=mod_obj.read(cr,uid,[vals.ref.id],['duration','class'],context) + defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id,'duration':res[0]['duration'],'class':res[0]['class']} mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context) self.write(cr, uid, vals.id, {'state': 'accepted'}, context) @@ -1012,7 +1013,7 @@ class calendar_event(osv.osv): 'Show Time as', states={'done': [('readonly', True)]}), 'base_calendar_url': fields.char('Caldav URL', size=264), 'state': fields.selection([ - ('tentative', 'Tentative'), + ('tentative', 'Uncertain'), ('cancelled', 'Cancelled'), ('confirmed', 'Confirmed'), ], 'Status', readonly=True), diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index 8612d20be1f..10c65ab850b 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -89,7 +89,8 @@ - + diff --git a/addons/base_calendar/i18n/base_calendar.pot b/addons/base_calendar/i18n/base_calendar.pot index 628f7af2baa..88ba245051f 100644 --- a/addons/base_calendar/i18n/base_calendar.pot +++ b/addons/base_calendar/i18n/base_calendar.pot @@ -1146,7 +1146,7 @@ msgstr "" #: selection:calendar.attendee,state:0 #: selection:calendar.event,state:0 #: selection:calendar.todo,state:0 -msgid "Tentative" +msgid "Uncertain" msgstr "" #. module: base_calendar diff --git a/addons/base_gengo/__openerp__.py b/addons/base_gengo/__openerp__.py index ae61ad8f13c..cc9183f6851 100644 --- a/addons/base_gengo/__openerp__.py +++ b/addons/base_gengo/__openerp__.py @@ -25,7 +25,8 @@ 'description': """ Automated Translations through Gengo API ---------------------------------------- - This module will install passive scheduler job for automated translations + +This module will install passive scheduler job for automated translations using the Gengo API. To activate it, you must 1) Configure your Gengo authentication parameters under `Settings > Companies > Gengo Parameters` 2) Launch the wizard under `Settings > Application Terms > Gengo: Manual Request of Translation` and follow the wizard. diff --git a/addons/base_import/__openerp__.py b/addons/base_import/__openerp__.py index f5a707650d8..6555cd8e3e0 100644 --- a/addons/base_import/__openerp__.py +++ b/addons/base_import/__openerp__.py @@ -26,7 +26,7 @@ Re-implement openerp's file import system: 'author': 'OpenERP SA', 'depends': ['base'], 'installable': True, - 'auto_install': True, + 'auto_install': False, 'css': [ 'static/lib/select2/select2.css', 'static/src/css/import.css', diff --git a/addons/base_import/static/csv/External_id_3rd_party_application_product_categories.csv b/addons/base_import/static/csv/External_id_3rd_party_application_product_categories.csv new file mode 100644 index 00000000000..5b77f269d85 --- /dev/null +++ b/addons/base_import/static/csv/External_id_3rd_party_application_product_categories.csv @@ -0,0 +1,6 @@ +External ID,Name,Parent Category/External ID +a1,Expenses,product.product_category_all +a2,Other Products,product.product_category_all +a3,Sellable Products,product.product_category_all +a4,Tables,a1 +a5,Seating furniture,a2 diff --git a/addons/base_import/static/csv/External_id_3rd_party_application_products.csv b/addons/base_import/static/csv/External_id_3rd_party_application_products.csv new file mode 100644 index 00000000000..bf2a5238696 --- /dev/null +++ b/addons/base_import/static/csv/External_id_3rd_party_application_products.csv @@ -0,0 +1,6 @@ +External ID,Name,Internal Reference,Category/External ID,Can be Expensed,Can be Purchased,Can be Sold,Sale Price,Cost,Supply Method,Product Type,Procurement Method +a6,Aluminum Stool,ALS,a5,False,True,True,49.00,25.00,Buy,Stockable Product,Make to Stock +a7,Chair,CHR,a5,False,True,True,89.00,40.00,Buy,Stockable Product,Make to Stock +a8,Table,TBL,a4,False,True,True,169.00,100.00,Buy,Stockable Product,Make to Stock +a9,Software Book Tutorial,SBT,a2,False,True,False,19.00,8.00,Buy,Consumable,Make to Stock +a10,Fuel,FL,a1,True,False,False,0.30,0.25,Buy,Service,Make to Stock diff --git a/addons/base_import/static/csv/Name_products.csv b/addons/base_import/static/csv/Name_products.csv new file mode 100644 index 00000000000..b3fa9b572bd --- /dev/null +++ b/addons/base_import/static/csv/Name_products.csv @@ -0,0 +1,6 @@ +Name,Internal Reference,Can be Expensed,Can be Purchased,Can be Sold,Sale Price,Cost,Supply Method,Product Type,Procurement Method +Aluminum Stool,ALS,False,True,True,49.00,25.00,Buy,Stockable Product,Make to Stock +Chair,CHR,False,True,True,89.00,40.00,Buy,Stockable Product,Make to Stock +Table,TBL,False,True,True,169.00,100.00,Buy,Stockable Product,Make to Stock +Software Book Tutorial,SBT,False,True,False,19.00,8.00,Buy,Consumable,Make to Stock +Fuel,FL,True,False,False,0.30,0.25,Buy,Service,Make to Stock diff --git a/addons/base_import/static/csv/database_import_test.sql b/addons/base_import/static/csv/database_import_test.sql new file mode 100644 index 00000000000..1f79179145e --- /dev/null +++ b/addons/base_import/static/csv/database_import_test.sql @@ -0,0 +1,155 @@ +-- +-- PostgreSQL database dump +-- + +SET statement_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = off; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET escape_string_warning = off; + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: companies; Type: TABLE; Schema: public; Owner: fp; Tablespace: +-- + +CREATE TABLE companies ( + id integer NOT NULL, + company_name character varying +); + + +ALTER TABLE public.companies OWNER TO fp; + +-- +-- Name: companies_id_seq; Type: SEQUENCE; Schema: public; Owner: fp +-- + +CREATE SEQUENCE companies_id_seq + START WITH 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; + + +ALTER TABLE public.companies_id_seq OWNER TO fp; + +-- +-- Name: companies_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp +-- + +ALTER SEQUENCE companies_id_seq OWNED BY companies.id; + + +-- +-- Name: companies_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp +-- + +SELECT pg_catalog.setval('companies_id_seq', 3, true); + + +-- +-- Name: persons; Type: TABLE; Schema: public; Owner: fp; Tablespace: +-- + +CREATE TABLE persons ( + id integer NOT NULL, + company_id integer, + person_name character varying +); + + +ALTER TABLE public.persons OWNER TO fp; + +-- +-- Name: persons_id_seq; Type: SEQUENCE; Schema: public; Owner: fp +-- + +CREATE SEQUENCE persons_id_seq + START WITH 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; + + +ALTER TABLE public.persons_id_seq OWNER TO fp; + +-- +-- Name: persons_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp +-- + +ALTER SEQUENCE persons_id_seq OWNED BY persons.id; + + +-- +-- Name: persons_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp +-- + +SELECT pg_catalog.setval('persons_id_seq', 4, true); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: fp +-- + +ALTER TABLE ONLY companies ALTER COLUMN id SET DEFAULT nextval('companies_id_seq'::regclass); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: fp +-- + +ALTER TABLE ONLY persons ALTER COLUMN id SET DEFAULT nextval('persons_id_seq'::regclass); + + +-- +-- Data for Name: companies; Type: TABLE DATA; Schema: public; Owner: fp +-- + +COPY companies (id, company_name) FROM stdin; +1 Bigees +2 Organi +3 Boum +\. + + +-- +-- Data for Name: persons; Type: TABLE DATA; Schema: public; Owner: fp +-- + +COPY persons (id, company_id, person_name) FROM stdin; +1 1 Fabien +2 1 Laurence +3 2 Eric +4 3 Ramsy +\. + + +-- +-- Name: companies_pkey; Type: CONSTRAINT; Schema: public; Owner: fp; Tablespace: +-- + +ALTER TABLE ONLY companies + ADD CONSTRAINT companies_pkey PRIMARY KEY (id); + + +-- +-- Name: persons_company_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: fp +-- + +ALTER TABLE ONLY persons + ADD CONSTRAINT persons_company_id_fkey FOREIGN KEY (company_id) REFERENCES companies(id); + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/addons/base_import/static/csv/m2m_customers_tags.csv b/addons/base_import/static/csv/m2m_customers_tags.csv new file mode 100644 index 00000000000..e6d74d4f844 --- /dev/null +++ b/addons/base_import/static/csv/m2m_customers_tags.csv @@ -0,0 +1,6 @@ +Name,Reference,Tags,Customer,Street,City,Country +Credit & Leasing,3,Services,True,Central Avenue 814,Johannesburg,South Africa +Services & Finance,5,"Consultancy Services,IT Services",True,Grove Road 5,London,United Kingdom +Hydra Supplies,6,"Manufacturer,Retailer",True,Palm Street 9,Los Angeles,United States +Bolts & Screws,8,"Wholesaler,Components Buyer",True,Rua Américo 1000,Campinas,Brazil +National Parts & Supplies,18,"Manufacturer,Wholesaler",True,Guangdong Way 20,Shenzen,China diff --git a/addons/base_import/static/csv/o2m_purchase_order_lines.csv b/addons/base_import/static/csv/o2m_purchase_order_lines.csv new file mode 100644 index 00000000000..4778b99cb81 --- /dev/null +++ b/addons/base_import/static/csv/o2m_purchase_order_lines.csv @@ -0,0 +1,10 @@ +Order Date,Order Reference,Supplier,Destination,Pricelist,Order Lines / Product,Order Lines / Quantity +2012-12-15,PO00008,ASUSTeK,Stock,Default Purchase Pricelist,ADPT,20 +,,,,,CARD,30 +,,,,,C-Case,40 +2012-12-15,PO00009,Axelor,Stock,Default Purchase Pricelist,CD,5 +,,,,,CPUa8,15 +2012-12-15,PO000010,China Export,Stock,Default Purchase Pricelist,HDD SH-1,10 +,,,,,HDD SH-2,20 +,,,,,LAP-CUS,35 +,,,,,LAP-E5,40 diff --git a/addons/base_import/static/csv/o2m_suppliers_contacts.csv b/addons/base_import/static/csv/o2m_suppliers_contacts.csv new file mode 100644 index 00000000000..6c7be9037b8 --- /dev/null +++ b/addons/base_import/static/csv/o2m_suppliers_contacts.csv @@ -0,0 +1,8 @@ +Name,Address type,Street,City,Country,Tags,Supplier,Customer,Is a company,Companies that refers to partner / Parent company +Wood y Wood Pecker,,"Snow Street, 25",Kainuu,Finland,Supplier,1,0,1, +Roger Pecker,Default,"Snow Street, 27",Kainuu,Finland,Supplier,1,0,0,Wood y Wood Pecker +Sharon Pecker,Delivery,"Snow Street, 28",Kainuu,Finland,Supplier,1,0,0,Wood y Wood Pecker +Thomas Pecker,Contact,"Snow Street, 27",Kainuu,Finland,Supplier,1,0,0,Wood y Wood Pecker +Vicking Direct,,"Atonium Street, 45a",Brussels,Belgium,Supplier,1,0,1, +Yvan Holiday,Invoice,"Atonium Street, 45b",Brussels,Belgium,Supplier,1,0,0,Vicking Direct +Jack Unsworth,Contact,"Atonium Street, 45a",Brussels,Belgium,Supplier,1,0,0,Vicking Direct diff --git a/addons/base_import/static/csv/purchase.order_functional_error_line_cant_adpat.csv b/addons/base_import/static/csv/purchase.order_functional_error_line_cant_adpat.csv new file mode 100644 index 00000000000..3b428763431 --- /dev/null +++ b/addons/base_import/static/csv/purchase.order_functional_error_line_cant_adpat.csv @@ -0,0 +1,6 @@ +"Order Reference","Supplier","Destination","Pricelist","Order Lines / Product","Order Lines / Quantity" +"PO000020","ASUSTeK","Stock","Default Purchase Pricelist","ADPT",20 +,,,,"CARD",30 +,,,,"C-Case",40 +"PO000021","Axelor","Stock","Default Purchase Pricelist","CD",5 +,,,,"CPUa8",15 diff --git a/addons/base_import/static/src/css/import.css b/addons/base_import/static/src/css/import.css index 47b5506a10f..00c8bd138a4 100644 --- a/addons/base_import/static/src/css/import.css +++ b/addons/base_import/static/src/css/import.css @@ -1,14 +1,17 @@ -.oe_import{ - display: inline-block; - width: 600px; - padding: 16px; -} .oe_import > p { margin-left: 8px; margin-right: 8px; text-align: justify } +.oe_import h2 { + margin-top: 0; + margin-bottom: 5px; +} + +.oe_padding { + padding: 13px; +} /* ----------- IMPORT BOX ----------- */ .oe_import .oe_import_box{ @@ -17,6 +20,7 @@ background: #F0EEEE; border-radius: 3px; border: solid 1px #dddddd; + width: 600px; } .oe_import .oe_import_toggle{ margin-top: 8px; diff --git a/addons/base_import/static/src/js/import.js b/addons/base_import/static/src/js/import.js index 407d38dacd7..1f11b3c1e86 100644 --- a/addons/base_import/static/src/js/import.js +++ b/addons/base_import/static/src/js/import.js @@ -123,10 +123,10 @@ openerp.base_import = function (instance) { this.exit(); } }, - init: function (parent, params) { + init: function (parent, action) { var self = this; this._super.apply(this, arguments); - this.res_model = params.model; + this.res_model = action.params.model; // import object id this.id = null; this.Import = new instance.web.Model('base_import.import'); @@ -179,7 +179,8 @@ openerp.base_import = function (instance) { //- File & settings change section onfile_loaded: function () { - this.$('.oe_import_button').prop('disabled', true); + this.$('.oe_import_button, .oe_import_file_reload') + .prop('disabled', true); if (!this.$('input.oe_import_file').val()) { return; } this.$el.removeClass('oe_import_preview oe_import_error'); @@ -189,7 +190,8 @@ openerp.base_import = function (instance) { }, onpreviewing: function () { var self = this; - this.$('.oe_import_button').prop('disabled', true); + this.$('.oe_import_button, .oe_import_file_reload') + .prop('disabled', true); this.$el.addClass('oe_import_with_file'); // TODO: test that write // succeeded? this.$el.removeClass('oe_import_preview_error oe_import_error'); @@ -205,6 +207,7 @@ openerp.base_import = function (instance) { }, onpreview_error: function (event, from, to, result) { this.$('.oe_import_options').show(); + this.$('.oe_import_file_reload').prop('disabled', false); this.$el.addClass('oe_import_preview_error oe_import_error'); this.$('.oe_import_error_report').html( QWeb.render('ImportView.preview.error', result)); @@ -212,7 +215,8 @@ openerp.base_import = function (instance) { onpreview_success: function (event, from, to, result) { this.$('.oe_import_import').removeClass('oe_highlight'); this.$('.oe_import_validate').addClass('oe_highlight'); - this.$('.oe_import_button').prop('disabled', false); + this.$('.oe_import_button, .oe_import_file_reload') + .prop('disabled', false); this.$el.addClass('oe_import_preview'); this.$('table').html(QWeb.render('ImportView.preview', result)); diff --git a/addons/base_import/static/src/xml/import.xml b/addons/base_import/static/src/xml/import.xml index 4f721032d1d..0da75eb69b9 100644 --- a/addons/base_import/static/src/xml/import.xml +++ b/addons/base_import/static/src/xml/import.xml @@ -2,20 +2,25 @@
-
- - - or - Cancel -
- - -

Upload your file

+
+
+

+ Import a CSV File +

+ + + + + or + Cancel +
+
+

Select the .CSV @@ -25,7 +30,9 @@ -

@@ -44,7 +51,7 @@
-
+

Map your data to OpenERP

@@ -59,6 +66,7 @@

Frequently Asked Questions

+
Need to import data from an other application?
@@ -78,9 +86,259 @@ whenever possible

+ +
+
+ What can I do when the Import preview table isn't + displayed correctly?
+
+

By default the Import preview is set on commas as + field separators and quotation marks as text + delimiters. If your csv file does not have these + settings, you can modify the File Format Options + (displayed under the Browse CSV file bar after you + select your file).

Note that if your CSV file + has a tabulation as separator, OpenERP will not + detect the separations. You will need to change the + file format options in your spreadsheet application. + See the following question.

+
+
+ +
+
+ How can I change the CSV file format options when + saving in my spreadsheet application?
+
+

If you edit and save CSV files in speadsheet + applications, your computer's regional settings will + be applied for the separator and delimiter. + We suggest you use OpenOffice or LibreOffice Calc + as they will allow you to modify all three options + (in 'Save As' dialog box > Check the box 'Edit filter + settings' > Save).

Microsoft Excel will allow + you to modify only the encoding when saving + (in 'Save As' dialog box > click 'Tools' dropdown + list > Encoding tab).

+
+
+ +
+
+ What's the difference between Database ID and + External ID?
+
+

Some fields define a relationship with another + object. For example, the country of a contact is a + link to a record of the 'Country' object. When you + want to import such fields, OpenERP will have to + recreate links between the different records. + To help you import such fields, OpenERP provides 3 + mechanisms. You must use one and only one mechanism + per field you want to import.

For example, to + reference the country of a contact, OpenERP proposes + you 3 different fields to import:

    +
  • Country: the name or code of the country
  • +
  • Country/Database ID: the unique OpenERP ID for a + record, defined by the ID postgresql column
  • +
  • Country/External ID: the ID of this record + referenced in another application (or the .XML file + that imported it)

For the country + Belgium, you can use one of these 3 ways to import: +

  • Country: Belgium
  • Country/Database + ID: 21
  • Country/External ID: base.be
  • +

According to your need, you should use + one of these 3 ways to reference records in relations. + Here is when you should use one or the other, + according to your need:

  • Use Country: This is + the easiest way when your data come from CSV files + that have been created manually.
  • Use + Country/Database ID: You should rarely use this + notation. It's mostly used by developers as it's main + advantage is to never have conflicts (you may have + several records with the same name, but they always + have a unique Database ID)
  • Use + Country/External ID: Use External ID when you import + data from a third party application.

+

When you use External IDs, you can import CSV files + with the "External ID" column to define the External + ID of each record you import. Then, you will be able + to make a reference to that record with columns like + "Field/External ID". The following two CSV files give + you an example for Products and their Categories.

+ CSV file for categories
+ CSV file for Products +
+
+ +
+
+ What can I do if I have multiple matches for a field? +
+
+

If for example you have two product categories + with the child name "Sellable" (ie. "Misc. + Products/Sellable" & "Other Products/Sellable"), + your validation is halted but you may still import + your data. However, we recommend you do not import the + data because they will all be linked to the first + 'Sellable' category found in the Product Category list + ("Misc. Products/Sellable"). We recommend you modify + one of the duplicates' values or your product category + hierarchy.
+ However if you do not wish to change your + configuration of product categories, we recommend you + use make use of the external ID for this field + 'Category'.

+
+
+ +
+
+ How can I import a many2many relationship field + (e.g. a customer that has multiple tags)?
+
+

The tags should be separated by a comma without any + spacing. For example, if you want you customer to be + lined to both tags 'Manufacturer' and 'Retailer' + then you will encode it as follow "Manufacturer, + Retailer" in the same column of your CSV file.

+ + CSV file for Manufacturer, Retailer
+
+
+ +
+
+ How can I import a one2many relationship (e.g. several + Order Lines of a Sale Order)?
+
+

If you want to import sales order having several + order lines; for each order line, you need to reserve + a specific row in the CSV file. The first order line + will be imported on the same row as the information + relative to order. Any additional lines will need an + addtional row that does not have any information in + the fields relative to the order.

+

As an example, here is + purchase.order_functional_error_line_cant_adpat.CSV + file of some quotations you can import, based on demo + data.

+ File for some Quotations +

The following CSV file shows how to import purchase + orders with their respective purchase order lines:

+ Purchase orders with their respective purchase order lines +

The following CSV file shows how to import + suppliers and their respective contacts

+ Suppliers and their respective contacts +
+
+ + +
+
+ Can I import several times the same record?
+
+

If you import a file that contains one of the + column "External ID" or "Database ID", records that + have already been imported will be modified instead of + being created. This is very usefull as it allows you + to import several times the same CSV file while having + made some changes in between two imports. OpenERP will + take care of creating or modifying each record + depending if it's new or not.

This feature + allows you to use the Import/Export tool of OpenERP to + modify a batch of records in your favorite spreadsheet + application.

+
+
+ +
+
+ What happens if I do not provide a value for a + specific field?
+
+

If you do not set all fields in your CSV file, + OpenERP will assign the default value for every non + defined fields. But if you + set fields with empty values in your CSV file, OpenERP + will set the EMPTY value in the field, instead of + assigning the default value.

+
+
+ +
+
+ How to export/import different tables from an SQL + application to OpenERP?
+
+

If you need to import data from different tables, + you will have to recreate relations between records + belonging to different tables. (e.g. if you import + companies and persons, you will have to recreate the + link between each person and the company they work + for).

To manage relations between tables, + you can use the "External ID" facilities of OpenERP. + The "External ID" of a record is the unique identifier + of this record in another application. This "External + ID" must be unique accoss all the records of all + objects, so it's a good practice to prefix this + "External ID" with the name of the application or + table. (like 'company_1', 'person_1' instead of '1') +

As an example, suppose you have a SQL database + with two tables you want to import: companies and + persons. Each person belong to one company, so you + will have to recreate the link between a person and + the company he work for. (If you want to test this + example, here is a + dump of such a PostgreSQL database).

+

We will first export all companies and their + "External ID". In PSQL, write the following command: +

    copy + (select 'company_'||id as "External ID",company_name + as "Name",'True' as "Is a Company" from companies) TO + '/tmp/company.csv' with CSV HEADER;

+

This SQL command will create the following CSV file: +
    External ID,Name,Is a Company +
    company_1,Bigees,True +
    company_2,Organi,True +
    company_3,Boum,True

+

To create the CSV file for persons, linked to + companies, we will use the following SQL command in + PSQL:

    copy (select + 'person_'||id as "External ID",person_name as + "Name",'False' as "Is a Company",'company_'||company_id + as "Related Company/External ID" from persons) TO + '/tmp/person.csv' with CSV

+

It will produce the following CSV file: +
    External ID,Name,Is a + Company,Related Company/External ID +
    person_1,Fabien,False,company_1 +
    person_2,Laurence,False,company_1 +
    person_3,Eric,False,company_2 +
    person_4,Ramsy,False,company_3

+

As you can see in this file, Fabien and Laurence + are working for the Bigees company (company_1) and + Eric is working for the Organi company. The relation + between persons and companies is done using the + External ID of the companies. We had to prefix the + "External ID" by the name of the table to avoid a + conflict of ID between persons and companies (person_1 + and company_1 who shared the same ID 1 in the orignial + database).

+

The two files produced are ready to be imported in + OpenERP without any modifications. After having + imported these two CSV files, you will have 4 contacts + and 3 companies. (the firsts two contacts are linked + to the first company). You must first import the + companies and then the persons.

+
+
+ + - - - - - - - - - - - - + + + - - - + + + + + + + + + @@ -70,7 +84,7 @@ - 0 + 0h00 - - + + + + + + + + + + + + diff --git a/addons/hr_attendance/wizard/hr_attendance_byweek.py b/addons/hr_attendance/wizard/hr_attendance_byweek.py index 454ba7764da..420773eaf13 100644 --- a/addons/hr_attendance/wizard/hr_attendance_byweek.py +++ b/addons/hr_attendance/wizard/hr_attendance_byweek.py @@ -18,7 +18,8 @@ # along with this program. If not, see . # ############################################################################## -import time +from datetime import datetime +from dateutil.relativedelta import relativedelta from osv import osv, fields @@ -30,8 +31,8 @@ class hr_attendance_byweek(osv.osv_memory): 'end_date': fields.date('Ending Date', required=True) } _defaults = { - 'init_date': lambda *a: time.strftime('%Y-%m-%d'), - 'end_date': lambda *a: time.strftime('%Y-%m-%d'), + 'init_date': (datetime.today() - relativedelta(days=datetime.date(datetime.today()).weekday())).strftime('%Y-%m-%d'), + 'end_date': (datetime.today() + relativedelta(days=6 - datetime.date(datetime.today()).weekday())).strftime('%Y-%m-%d'), } def print_report(self, cr, uid, ids, context=None): diff --git a/addons/hr_evaluation/__openerp__.py b/addons/hr_evaluation/__openerp__.py index fb3d87f2bc1..fb303ff28e4 100644 --- a/addons/hr_evaluation/__openerp__.py +++ b/addons/hr_evaluation/__openerp__.py @@ -24,6 +24,7 @@ 'version': '0.1', 'author': 'OpenERP SA', 'category': 'Human Resources', + 'sequence': 31, 'website': 'http://www.openerp.com', 'summary': 'Periodical Evaluations, Appraisals, Surveys', 'images': ['images/hr_evaluation_analysis.jpeg','images/hr_evaluation.jpeg'], diff --git a/addons/hr_expense/__openerp__.py b/addons/hr_expense/__openerp__.py index 4b273a9cc81..ec108b696bf 100644 --- a/addons/hr_expense/__openerp__.py +++ b/addons/hr_expense/__openerp__.py @@ -24,7 +24,7 @@ 'name': 'Expense Management', 'version': '1.0', 'category': 'Human Resources', - 'sequence': 30, + 'sequence': 29, 'summary': 'Expenses Validation, Invoicing', 'description': """ Manage expenses by Employees diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index 439cf683d1b..c9961fa8a1d 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -233,7 +233,7 @@ hr_expense_expense() class product_product(osv.osv): _inherit = "product.product" _columns = { - 'hr_expense_ok': fields.boolean('Can be Expensed', help="Determines if the product can be visible in the list of product within a selection from an HR expense sheet line."), + 'hr_expense_ok': fields.boolean('Can be Expensed', help="Specify if the product can be selected in an HR expense line."), } def on_change_hr_expense_ok(self, cr, uid, id, hr_expense_ok): diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 935dbb4da2b..fbaa4c1f87e 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -123,8 +123,8 @@ - - + + @@ -188,8 +188,6 @@ - - product.product.expense.form @@ -203,5 +201,20 @@ + + Products + product.product + form + kanban,tree,form + {"default_hr_expense_ok":1} + [('hr_expense_ok','=',True)] + + + + + + + + diff --git a/addons/hr_holidays/__openerp__.py b/addons/hr_holidays/__openerp__.py index 3cf4a30a2e1..417748d874b 100644 --- a/addons/hr_holidays/__openerp__.py +++ b/addons/hr_holidays/__openerp__.py @@ -25,7 +25,7 @@ 'version': '1.5', 'author': 'OpenERP SA', 'category': 'Human Resources', - 'sequence': 28, + 'sequence': 27, 'summary': 'Holidays, Allocation and Leave Requests', 'website': 'http://www.openerp.com', 'description': """ diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 0977f3729b4..f315f3d4ef3 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -27,6 +27,7 @@ from operator import itemgetter import math import netsvc +import tools from osv import fields, osv from tools.translate import _ @@ -147,10 +148,10 @@ class hr_holidays(osv.osv): } _sql_constraints = [ ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))", "The employee or employee category of this request is missing."), - ('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be before the end date !"), - ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0 !"), + ('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."), + ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."), ] - + def _create_resource_leave(self, cr, uid, leaves, context=None): '''This method will create entry in resource calendar leave object at the time of holidays validated ''' obj_res_leave = self.pool.get('resource.calendar.leaves') @@ -182,6 +183,13 @@ class hr_holidays(osv.osv): } return result + def onchange_employee(self, cr, uid, ids, employee_id): + result = {'value': {'department_id': False}} + if employee_id: + employee = self.pool.get('hr.employee').browse(cr, uid, employee_id) + result['value'] = {'department_id': employee.department_id.id} + return result + # TODO: can be improved using resource calendar method def _get_number_of_days(self, date_from, date_to): """Returns a float equals to the timedelta between two dates given as string.""" @@ -196,20 +204,53 @@ class hr_holidays(osv.osv): def unlink(self, cr, uid, ids, context=None): for rec in self.browse(cr, uid, ids, context=context): if rec.state not in ['draft', 'cancel', 'confirm']: - raise osv.except_osv(_('Warning!'),_('You cannot delete a leave which is in %s state!')%(rec.state)) + raise osv.except_osv(_('Warning!'),_('You cannot delete a leave which is in %s state.')%(rec.state)) return super(hr_holidays, self).unlink(cr, uid, ids, context) def onchange_date_from(self, cr, uid, ids, date_to, date_from): - result = {} - if date_to and date_from: + """ + If there are no date set for date_to, automatically set one 8 hours later than + the date_from. + Also update the number_of_days. + """ + # date_to has to be greater than date_from + if (date_from and date_to) and (date_from > date_to): + raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.')) + + result = {'value': {}} + + # No date_to set so far: automatically compute one 8 hours later + if date_from and not date_to: + date_to_with_delta = datetime.datetime.strptime(date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT) + datetime.timedelta(hours=8) + result['value']['date_to'] = str(date_to_with_delta) + + # Compute and update the number of days + if (date_to and date_from) and (date_from <= date_to): diff_day = self._get_number_of_days(date_from, date_to) - result['value'] = { - 'number_of_days_temp': round(math.floor(diff_day))+1 - } - return result - result['value'] = { - 'number_of_days_temp': 0, - } + result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1 + else: + result['value']['number_of_days_temp'] = 0 + + return result + + def onchange_date_to(self, cr, uid, ids, date_to, date_from): + """ + Update the number_of_days. + """ + + # date_to has to be greater than date_from + if (date_from and date_to) and (date_from > date_to): + raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.')) + + result = {'value': {}} + + # Compute and update the number of days + if (date_to and date_from) and (date_from <= date_to): + diff_day = self._get_number_of_days(date_from, date_to) + result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1 + else: + result['value']['number_of_days_temp'] = 0 + return result def set_to_draft(self, cr, uid, ids, context=None): @@ -430,10 +471,10 @@ class hr_employee(osv.osv): def _get_remaining_days(self, cr, uid, ids, name, args, context=None): cr.execute("""SELECT sum(h.number_of_days) as days, - h.employee_id + h.employee_id from hr_holidays h - join hr_holidays_status s on (s.id=h.holiday_status_id) + join hr_holidays_status s on (s.id=h.holiday_status_id) where h.state='validate' and s.limit=False and @@ -448,9 +489,9 @@ class hr_employee(osv.osv): remaining[employee_id] = 0.0 return remaining - def _get_leave_status(self, cr, uid, ids, name, args, context=None): + def _get_leave_status(self, cr, uid, ids, name, args, context=None): holidays_obj = self.pool.get('hr.holidays') - holidays_id = holidays_obj.search(cr, uid, + holidays_id = holidays_obj.search(cr, uid, [('employee_id', 'in', ids), ('date_from','<=',time.strftime('%Y-%m-%d %H:%M:%S')), ('date_to','>=',time.strftime('%Y-%m-%d 23:59:59')),('type','=','remove'),('state','not in',('cancel','refuse'))], context=context) @@ -476,7 +517,7 @@ class hr_employee(osv.osv): ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]), 'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type",type='many2one', relation='hr.holidays.status'), 'leave_date_from': fields.function(_get_leave_status, multi='leave_status', type='date', string='From Date'), - 'leave_date_to': fields.function(_get_leave_status, multi='leave_status', type='date', string='To Date'), + 'leave_date_to': fields.function(_get_leave_status, multi='leave_status', type='date', string='To Date'), } hr_employee() diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 7ccace1c536..4691f7ebd71 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -13,9 +13,9 @@ action_holidays_unread - + action - + hr.holidays client_action_multi @@ -31,9 +31,9 @@ action_holidays_read - + action - + hr.holidays client_action_multi @@ -46,7 +46,7 @@ - + @@ -83,7 +83,7 @@ - + Leave Request hr.holidays 1 @@ -93,7 +93,7 @@ + [[ formatLang(getsumdisc(data['form']), dp='Sale Price', currency_obj = company.currency_id) ]] + + diff --git a/addons/point_of_sale/wizard/pos_payment.py b/addons/point_of_sale/wizard/pos_payment.py index ca9331012b7..9511aed6331 100644 --- a/addons/point_of_sale/wizard/pos_payment.py +++ b/addons/point_of_sale/wizard/pos_payment.py @@ -43,8 +43,6 @@ class account_journal(osv.osv): return super(account_journal, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count) -account_journal() - class pos_make_payment(osv.osv_memory): _name = 'pos.make.payment' _description = 'Point of Sale Payment' diff --git a/addons/procurement/procurement.py b/addons/procurement/procurement.py index 55a291bd3c2..c166f79e524 100644 --- a/addons/procurement/procurement.py +++ b/addons/procurement/procurement.py @@ -561,10 +561,10 @@ class stock_warehouse_orderpoint(osv.osv): 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True), 'product_min_qty': fields.float('Minimum Quantity', required=True, help="When the virtual stock goes below the Min Quantity specified for this field, OpenERP generates "\ - "a procurement to bring the virtual stock to the Max Quantity."), + "a procurement to bring the forecasted quantity to the Max Quantity."), 'product_max_qty': fields.float('Maximum Quantity', required=True, help="When the virtual stock goes below the Min Quantity, OpenERP generates "\ - "a procurement to bring the virtual stock to the Quantity specified as Max Quantity."), + "a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity."), 'qty_multiple': fields.integer('Qty Multiple', required=True, help="The procurement quantity will be rounded up to this multiple."), 'procurement_id': fields.many2one('procurement.order', 'Latest procurement', ondelete="set null"), @@ -633,9 +633,9 @@ class product_template(osv.osv): _inherit="product.template" _columns = { - 'type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Will change the way procurements are processed. Consumable are product where you don't manage stock, a service is a non-material product provided by a company or an individual."), - 'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', required=True, help="'Make to Stock': When needed, take from the stock or wait until re-supplying. 'Make to Order': When needed, purchase or produce for the procurement request."), - 'supply_method': fields.selection([('produce','Manufacture'),('buy','Buy')], 'Supply Method', required=True, help="Produce will generate production order or tasks, according to the product type. Buy will trigger purchase orders when requested."), + 'type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Consumable: Will not imply stock management for this product. \nStockable product: Will imply stock management for this product."), + 'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', required=True, help="Make to Stock: When needed, the product is taken from the stock or we wait for replenishment. \nMake to Order: When needed, the product is purchased or produced."), + 'supply_method': fields.selection([('produce','Manufacture'),('buy','Buy')], 'Supply Method', required=True, help="Manufacture: When procuring the product, a manufacturing order or a task will be generated, depending on the product type. \nBuy: When procuring the product, a purchase order will be generated."), } _defaults = { 'procure_method': 'make_to_stock', diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py index f5c69c7acf7..60736323054 100644 --- a/addons/product/pricelist.py +++ b/addons/product/pricelist.py @@ -95,7 +95,7 @@ class product_pricelist(osv.osv): _description = "Pricelist" _columns = { 'name': fields.char('Pricelist Name',size=64, required=True, translate=True), - 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the pricelist without removing it."), + 'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the pricelist without removing it."), 'type': fields.selection(_pricelist_type_get, 'Pricelist Type', required=True), 'version_id': fields.one2many('product.pricelist.version', 'pricelist_id', 'Pricelist Versions'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True), @@ -319,8 +319,8 @@ class product_pricelist_version(osv.osv): "and reactivate the pricelist"), 'items_id': fields.one2many('product.pricelist.item', 'price_version_id', 'Price List Items', required=True), - 'date_start': fields.date('Start Date', help="Starting date for this pricelist version to be valid."), - 'date_end': fields.date('End Date', help="Ending date for this pricelist version to be valid."), + 'date_start': fields.date('Start Date', help="First valid date for the version."), + 'date_end': fields.date('End Date', help="Last valid date for the version."), 'company_id': fields.related('pricelist_id','company_id',type='many2one', readonly=True, relation='res.company', string='Company', store=True) } @@ -403,13 +403,13 @@ class product_pricelist_item(osv.osv): _columns = { 'name': fields.char('Rule Name', size=64, help="Explicit rule name for this pricelist line."), 'price_version_id': fields.many2one('product.pricelist.version', 'Price List Version', required=True, select=True, ondelete='cascade'), - 'product_tmpl_id': fields.many2one('product.template', 'Product Template', ondelete='cascade', help="Set a template if this rule only apply to a template of product. Keep empty for all products"), - 'product_id': fields.many2one('product.product', 'Product', ondelete='cascade', help="Set a product if this rule only apply to one product. Keep empty for all products"), - 'categ_id': fields.many2one('product.category', 'Product Category', ondelete='cascade', help="Set a category of product if this rule only apply to products of a category and his children. Keep empty for all products"), + 'product_tmpl_id': fields.many2one('product.template', 'Product Template', ondelete='cascade', help="Specify a template if this rule only applies to one product template. Keep empty otherwise."), + 'product_id': fields.many2one('product.product', 'Product', ondelete='cascade', help="Specify a product if this rule only applies to one product. Keep empty otherwise."), + 'categ_id': fields.many2one('product.category', 'Product Category', ondelete='cascade', help="Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise."), - 'min_quantity': fields.integer('Min. Quantity', required=True, help="The rule only applies if the partner buys/sells equal to or more than this quantity."), + 'min_quantity': fields.integer('Min. Quantity', required=True, help="Specify the minimum quantity that needs to be bought/sold for the rule to apply."), 'sequence': fields.integer('Sequence', required=True, help="Gives the order in which the pricelist items will be checked. The evaluation gives highest priority to lowest sequence and stops as soon as a matching item is found."), - 'base': fields.selection(_price_field_get, 'Based on', required=True, size=-1, help="The mode for computing the price for this rule."), + 'base': fields.selection(_price_field_get, 'Based on', required=True, size=-1, help="Base price for computation."), 'base_pricelist_id': fields.many2one('product.pricelist', 'Other Pricelist'), 'price_surcharge': fields.float('Price Surcharge', diff --git a/addons/product/product.py b/addons/product/product.py index 6c6c5f2650c..b6a58fcd1e8 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -111,9 +111,9 @@ class product_uom(osv.osv): _columns = { 'name': fields.char('Unit of Measure', size=64, required=True, translate=True), 'category_id': fields.many2one('product.uom.categ', 'Category', required=True, ondelete='cascade', - help="Quantity conversions may happen automatically between Units of Measure in the same category, according to their respective ratios."), + help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios."), 'factor': fields.float('Ratio', required=True,digits=(12, 12), - help='How many times this Unit of Measure is smaller than the reference Unit of Measure in this category:\n'\ + help='How much bigger or smaller this unit is compared to the reference Unit of Measure for this category:\n'\ '1 * (reference unit) = ratio * (this unit)'), 'factor_inv': fields.function(_factor_inv, digits=(12,12), fnct_inv=_factor_inv_write, @@ -233,7 +233,7 @@ class product_category(osv.osv): 'parent_id': fields.many2one('product.category','Parent Category', select=True, ondelete='cascade'), 'child_id': fields.one2many('product.category', 'parent_id', string='Child Categories'), 'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of product categories."), - 'type': fields.selection([('view','View'), ('normal','Normal')], 'Category Type'), + 'type': fields.selection([('view','View'), ('normal','Normal')], 'Category Type', help="A category of the view type is a virtual category that can be used as the parent of another category to create a hierarchical structure."), 'parent_left': fields.integer('Left Parent', select=1), 'parent_right': fields.integer('Right Parent', select=1), } @@ -276,34 +276,34 @@ class product_template(osv.osv): _columns = { 'name': fields.char('Name', size=128, required=True, translate=True, select=True), - 'product_manager': fields.many2one('res.users','Product Manager',help="Responsible for product."), + 'product_manager': fields.many2one('res.users','Product Manager'), 'description': fields.text('Description',translate=True), 'description_purchase': fields.text('Purchase Description',translate=True), 'description_sale': fields.text('Sale Description',translate=True), 'type': fields.selection([('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Consumable are product where you don't manage stock, a service is a non-material product provided by a company or an individual."), - 'produce_delay': fields.float('Manufacturing Lead Time', help="Average delay in days to produce this product. This is only for the production order and, if it is a multi-level bill of material, it's only for the level of this product. Different lead times will be summed for all levels and purchase orders."), + 'produce_delay': fields.float('Manufacturing Lead Time', help="Average delay in days to produce this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added."), 'rental': fields.boolean('Can be Rent'), 'categ_id': fields.many2one('product.category','Category', required=True, change_default=True, domain="[('type','=','normal')]" ,help="Select category for the current product"), - 'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Product Price'), help="Base price for computing the customer price. Sometimes called the catalog price."), - 'standard_price': fields.float('Cost', digits_compute=dp.get_precision('Product Price'), help="Product's cost for accounting stock valuation. It is the base price for the supplier price.", groups="base.group_user"), + 'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Product Price'), help="Base price to compute the customer price. Sometimes called the catalog price."), + 'standard_price': fields.float('Cost', digits_compute=dp.get_precision('Product Price'), help="Cost price of the product used for standard stock valuation in accounting and used as a base price on purchase orders.", groups="base.group_user"), 'volume': fields.float('Volume', help="The volume in m3."), 'weight': fields.float('Gross Weight', digits_compute=dp.get_precision('Stock Weight'), help="The gross weight in Kg."), 'weight_net': fields.float('Net Weight', digits_compute=dp.get_precision('Stock Weight'), help="The net weight in Kg."), 'cost_method': fields.selection([('standard','Standard Price'), ('average','Average Price')], 'Costing Method', required=True, - help="Standard Price: the cost price is fixed and recomputed periodically (usually at the end of the year), Average Price: the cost price is recomputed at each reception of products."), + help="Standard Price: The cost price is manually updated at the end of a specific period (usually every year). \nAverage Price: The cost price is recomputed at each incoming shipment."), 'warranty': fields.float('Warranty'), - 'sale_ok': fields.boolean('Can be Sold', help="Determines if the product can be visible in the list of product within a selection from a sale order line."), + 'sale_ok': fields.boolean('Can be Sold', help="Specify if the product can be selected in a sales order line."), 'state': fields.selection([('',''), ('draft', 'In Development'), ('sellable','Normal'), ('end','End of Lifecycle'), - ('obsolete','Obsolete')], 'Status', help="Tells the user if he can use the product or not."), + ('obsolete','Obsolete')], 'Status'), 'uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True, help="Default Unit of Measure used for all stock operation."), 'uom_po_id': fields.many2one('product.uom', 'Purchase Unit of Measure', required=True, help="Default Unit of Measure used for purchase orders. It must be in the same category than the default unit of measure."), 'uos_id' : fields.many2one('product.uom', 'Unit of Sale', - help='Used by companies that manage two units of measure: invoicing and inventory management. For example, in food industries, you will manage a stock of ham but invoice in Kg. Keep empty to use the default Unit of Measure.'), + help='Sepcify a unit of measure here if invoicing is made in another unit of measure than inventory. Keep empty to use the default unit of measure.'), 'uos_coeff': fields.float('Unit of Measure -> UOS Coeff', digits_compute= dp.get_precision('Product UoS'), - help='Coefficient to convert Unit of Measure to UOS\n' + help='Coefficient to convert default Unit of Measure to Unit of Sale\n' ' uos = uom * coeff'), 'mes_type': fields.selection((('fixed', 'Fixed'), ('variable', 'Variable')), 'Measure Type'), 'seller_ids': fields.one2many('product.supplierinfo', 'product_id', 'Partners'), @@ -527,10 +527,10 @@ class product_product(osv.osv): 'code': fields.function(_product_code, type='char', string='Internal Reference'), 'partner_ref' : fields.function(_product_partner_ref, type='char', string='Customer ref'), 'default_code' : fields.char('Internal Reference', size=64, select=True), - 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the product without removing it."), + 'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the product without removing it."), 'variants': fields.char('Variants', size=64), 'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade"), - 'ean13': fields.char('EAN13 Barcode', size=13, help="The numbers encoded in EAN-13 bar codes are product identification numbers."), + 'ean13': fields.char('EAN13 Barcode', size=13, help="International Article Number used for product identification."), 'packaging' : fields.one2many('product.packaging', 'product_id', 'Logistical Units', help="Gives the different ways to package the same product. This has no impact on the picking order and is mainly used if you use the EDI module."), 'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Product Price')), 'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Product Price')), @@ -763,8 +763,7 @@ class product_packaging(osv.osv): help="The code of the transport unit."), 'weight': fields.float('Total Package Weight', help='The weight of a full package, pallet or box.'), - 'weight_ul': fields.float('Empty Package Weight', - help='The weight of the empty UL'), + 'weight_ul': fields.float('Empty Package Weight'), 'height': fields.float('Height', help='The height of the package'), 'width': fields.float('Width', help='The width of the package'), 'length': fields.float('Length', help='The length of the package'), diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 028e416984b..7b4197305d4 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -96,7 +96,7 @@ - + @@ -318,17 +318,11 @@ - + - - - - - - diff --git a/addons/product_visible_discount/product_visible_discount.py b/addons/product_visible_discount/product_visible_discount.py index 48e3c4a8283..008c1f313a6 100644 --- a/addons/product_visible_discount/product_visible_discount.py +++ b/addons/product_visible_discount/product_visible_discount.py @@ -111,9 +111,11 @@ class account_invoice_line(osv.osv): if res_dict.get('item_id',False) and res_dict['item_id'].get(pricelist,False): item = res_dict['item_id'].get(pricelist,False) - item_base = item_obj.read(cr, uid, [item], ['base'])[0]['base'] - if item_base > 0: - field_name = price_type_obj.browse(cr, uid, item_base).field + item_read = item_obj.read(cr, uid, [item], ['base']) + if item_read: + item_base = item_read[0]['base'] + if item_base > 0: + field_name = price_type_obj.browse(cr, uid, item_base).field product = product_obj.browse(cr, uid, product_id, context) product_read = product_obj.read(cr, uid, product_id, [field_name], context=context) diff --git a/addons/project/project.py b/addons/project/project.py index 3b0057c67b7..62c1b34dbf9 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -178,7 +178,18 @@ class project(osv.osv): res = super(project, self).unlink(cr, uid, ids, *args, **kwargs) mail_alias.unlink(cr, uid, alias_ids, *args, **kwargs) return res - + + def _get_attached_docs(self, cr, uid, ids, field_name, arg, context): + res = {} + attachment = self.pool.get('ir.attachment') + task = self.pool.get('project.task') + for id in ids: + project_attachments = attachment.search(cr, uid, [('res_model', '=', 'project.project'), ('res_id', 'in', [id])], context=context, count=True) + task_ids = task.search(cr, uid, [('project_id', 'in', [id])], context=context) + task_attachments = attachment.search(cr, uid, [('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)], context=context, count=True) + res[id] = project_attachments or 0 + task_attachments or 0 + return res + def _task_count(self, cr, uid, ids, field_name, arg, context=None): res = dict.fromkeys(ids, 0) task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)]) @@ -189,7 +200,26 @@ class project(osv.osv): def _get_alias_models(self, cr, uid, context=None): """Overriden in project_issue to offer more options""" return [('project.task', "Tasks")] - + + def attachment_tree_view(self, cr, uid, ids, context): + task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)]) + domain = [ + ('|', + '&', 'res_model', '=', 'project.project'), ('res_id', 'in', ids), + '&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids) + ] + res_id = ids and ids[0] or False + return { + 'name': _('Attachments'), + 'domain': domain, + 'res_model': 'ir.attachment', + 'type': 'ir.actions.act_window', + 'view_id': False, + 'view_mode': 'tree,form', + 'view_type': 'form', + 'limit': 80, + 'context': "{'default_res_model': '%s','default_res_id': %d}" % (self._name, res_id) + } # Lambda indirection method to avoid passing a copy of the overridable method when declaring the field _alias_models = lambda self, *args, **kwargs: self._get_alias_models(*args, **kwargs) _columns = { @@ -232,6 +262,7 @@ class project(osv.osv): help="The kind of document created when an email is received on this project's email alias"), 'privacy_visibility': fields.selection([('public','Public'), ('followers','Followers Only')], 'Privacy / Visibility', required=True), 'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','Pending'),('close','Closed')], 'Status', required=True,), + 'doc_count':fields.function(_get_attached_docs, string="Number of documents attached", type='int') } def _get_type_common(self, cr, uid, context): @@ -728,11 +759,11 @@ class task(base_stage, osv.osv): If the case needs to be reviewed then the status is \ set to \'Pending\'.'), 'categ_ids': fields.many2many('project.category', string='Tags'), - 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready To Pull')], 'Kanban State', + 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State', help="A task's kanban state indicates special situations affecting it:\n" " * Normal is the default situation\n" " * Blocked indicates something is preventing the progress of this task\n" - " * Ready To Pull indicates the task is ready to be pulled to the next stage", + " * Ready for next stage indicates the task is ready to be pulled to the next stage", readonly=True, required=False), 'create_date': fields.datetime('Create Date', readonly=True,select=True), 'date_start': fields.datetime('Starting Date',select=True), @@ -792,6 +823,11 @@ class task(base_stage, osv.osv): """ return self.write(cr, uid, ids, {'priority' : priority}) + def set_very_high_priority(self, cr, uid, ids, *args): + """Set task priority to very high + """ + return self.set_priority(cr, uid, ids, '0') + def set_high_priority(self, cr, uid, ids, *args): """Set task priority to high """ @@ -1093,13 +1129,36 @@ class task(base_stage, osv.osv): }, context=context) return True + def _subscribe_project_followers_to_task(self, cr, uid, task_id, context=None): + """ TDE note: not the best way to do this, we could override _get_followers + of task, and perform a better mapping of subtypes than a mapping + based on names. + However we will keep this implementation, maybe to be refactored + in 7.1 of future versions. """ + # task followers are project followers, with matching subtypes + task_record = self.browse(cr, uid, task_id, context=context) + subtype_obj = self.pool.get('mail.message.subtype') + follower_obj = self.pool.get('mail.followers') + if task_record.project_id: + # create mapping + task_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('res_model', '=', self._name)], context=context) + task_subtypes = subtype_obj.browse(cr, uid, task_subtype_ids, context=context) + # fetch subscriptions + follower_ids = follower_obj.search(cr, uid, [('res_model', '=', 'project.project'), ('res_id', '=', task_record.project_id.id)], context=context) + # copy followers + for follower in follower_obj.browse(cr, uid, follower_ids, context=context): + if not follower.subtype_ids: + continue + project_subtype_names = [project_subtype.name for project_subtype in follower.subtype_ids] + task_subtype_ids = [task_subtype.id for task_subtype in task_subtypes if task_subtype.name in project_subtype_names] + self.message_subscribe(cr, uid, [task_id], [follower.partner_id.id], + subtype_ids=task_subtype_ids, context=context) + def create(self, cr, uid, vals, context=None): task_id = super(task, self).create(cr, uid, vals, context=context) - task_record = self.browse(cr, uid, task_id, context=context) - if task_record.project_id: - project_follower_ids = [follower.id for follower in task_record.project_id.message_follower_ids] - self.message_subscribe(cr, uid, [task_id], project_follower_ids, - context=context) + # subscribe project followers to the task + self._subscribe_project_followers_to_task(cr, uid, task_id, context=context) + self._store_history(cr, uid, [task_id], context=context) self.create_send_note(cr, uid, [task_id], context=context) return task_id @@ -1109,9 +1168,6 @@ class task(base_stage, osv.osv): def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] - if vals.get('project_id'): - project_id = self.pool.get('project.project').browse(cr, uid, vals.get('project_id'), context=context) - vals['message_follower_ids'] = [(4, follower.id) for follower in project_id.message_follower_ids] if vals and not 'kanban_state' in vals and 'stage_id' in vals: new_stage = vals.get('stage_id') vals_reset_kstate = dict(vals, kanban_state='normal') @@ -1121,13 +1177,18 @@ class task(base_stage, osv.osv): #if new_stage not in stages: #raise osv.except_osv(_('Warning!'), _('Stage is not defined in the project.')) write_vals = vals_reset_kstate if t.stage_id != new_stage else vals - super(task,self).write(cr, uid, [t.id], write_vals, context=context) + super(task, self).write(cr, uid, [t.id], write_vals, context=context) self.stage_set_send_note(cr, uid, [t.id], new_stage, context=context) result = True else: - result = super(task,self).write(cr, uid, ids, vals, context=context) + result = super(task, self).write(cr, uid, ids, vals, context=context) if ('stage_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals): self._store_history(cr, uid, ids, context=context) + + # subscribe new project followers to the task + if vals.get('project_id'): + for id in ids: + self._subscribe_project_followers_to_task(cr, uid, id, context=context) return result def unlink(self, cr, uid, ids, context=None): @@ -1289,7 +1350,7 @@ class account_analytic_account(osv.osv): _inherit = 'account.analytic.account' _description = 'Analytic Account' _columns = { - 'use_tasks': fields.boolean('Tasks',help="If check,this contract will be available in the project menu and you will be able to manage tasks or track issues"), + 'use_tasks': fields.boolean('Tasks',help="If checked, this contract will be available in the project menu and you will be able to manage tasks or track issues"), 'company_uom_id': fields.related('company_id', 'project_time_mode_id', type='many2one', relation='product.uom'), } @@ -1398,7 +1459,7 @@ class project_task_history(osv.osv): 'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True, select=True), 'type_id': fields.many2one('project.task.type', 'Stage'), 'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done')], 'Status'), - 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready To Pull')], 'Kanban State', required=False), + 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State', required=False), 'date': fields.date('Date', select=True), 'end_date': fields.function(_get_date, string='End Date', type="date", store={ 'project.task.history': (_get_related_date, None, 20) diff --git a/addons/project/project_data.xml b/addons/project/project_data.xml index 560c1c3552a..4f467c89a29 100644 --- a/addons/project/project_data.xml +++ b/addons/project/project_data.xml @@ -86,37 +86,37 @@ - New Task + New project.project - Task Closed + Closed project.project - Task Canceled - project.task + Canceled + project.project - Task Stage Changed - project.task + Stage Changed + project.project - New Task + New project.task - Task Closed + Closed project.task - Task canceled + Canceled project.task - Task Stage Changed + Stage Changed project.task diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 29be26bc127..ec29b19e97b 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -63,8 +63,8 @@
-
@@ -403,12 +410,15 @@ + + groups="project.group_time_work_estimation_tasks" attrs="{'invisible':[('state','=','cancelled')]}"/> @@ -458,9 +468,6 @@ - @@ -525,10 +532,11 @@
- + - 7 - 7 + 7 + 7 + 7
+ - - - - - @@ -380,7 +376,9 @@ - Issues + Issues + Issue + diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml index 6bee4e38842..47ea91fad5f 100644 --- a/addons/project_issue/report/project_issue_report_view.xml +++ b/addons/project_issue/report/project_issue_report_view.xml @@ -63,7 +63,6 @@ - diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml index 69fe335c1b0..52eb5d80a3a 100644 --- a/addons/project_long_term/project_long_term_view.xml +++ b/addons/project_long_term/project_long_term_view.xml @@ -120,7 +120,10 @@ Phases() + name="%(act_project_phases)d" type="action"> + Phases() + Phase() +
diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py index fe4b848f522..439ff6d835d 100644 --- a/addons/project_timesheet/project_timesheet.py +++ b/addons/project_timesheet/project_timesheet.py @@ -86,7 +86,7 @@ class project_work(osv.osv): emp = emp_obj.browse(cr, uid, emp_id[0]) if not emp.product_id: raise osv.except_osv(_('Bad Configuration !'), - _('Please define product on the related employee.\nFill in the timesheet tab of the employee form.')) + _('Please define product and product category property account on the related employee.\nFill in the HR Settings tab of the employee form.')) if not emp.journal_id: raise osv.except_osv(_('Bad Configuration !'), diff --git a/addons/project_timesheet/project_timesheet_view.xml b/addons/project_timesheet/project_timesheet_view.xml index 81935b3434b..b8f5675891c 100644 --- a/addons/project_timesheet/project_timesheet_view.xml +++ b/addons/project_timesheet/project_timesheet_view.xml @@ -86,11 +86,9 @@ {'search_default_to_invoice': 1} -

- Click to add a work to invoice. -

- You will find here all works made on tasks that you can - invoice. +

+ You will find here timesheets and purchases you did for contracts that can be reinvoiced to the customer. + If you want to record new jobs to invoice, you should use the timesheet menu instead.

diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 991a60ef835..53e70dd4fb7 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -137,6 +137,17 @@ class purchase_order(osv.osv): invoiced = True res[purchase.id] = invoiced return res + + def _get_journal(self, cr, uid, context=None): + if context is None: + context = {} + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + company_id = context.get('company_id', user.company_id.id) + journal_obj = self.pool.get('account.journal') + res = journal_obj.search(cr, uid, [('type', '=', 'purchase'), + ('company_id', '=', company_id)], + limit=1) + return res and res[0] or False STATE_SELECTION = [ ('draft', 'Draft PO'), @@ -204,6 +215,7 @@ class purchase_order(osv.osv): 'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'), 'create_uid': fields.many2one('res.users', 'Responsible'), 'company_id': fields.many2one('res.company','Company',required=True,select=1), + 'journal_id': fields.many2one('account.journal', 'Journal'), } _defaults = { 'date_order': fields.date.context_today, @@ -214,6 +226,7 @@ class purchase_order(osv.osv): 'invoiced': 0, 'pricelist_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').browse(cr, uid, context['partner_id']).property_product_pricelist_purchase.id, 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c), + 'journal_id': _get_journal, } _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'), @@ -890,14 +903,16 @@ class purchase_order_line(osv.osv): account_tax = self.pool.get('account.tax') # - check for the presence of partner_id and pricelist_id - if not partner_id: - raise osv.except_osv(_('No Partner!'), _('Select a partner in purchase order to choose a product.')) - if not pricelist_id: - raise osv.except_osv(_('No Pricelist !'), _('Select a price list in the purchase order form before choosing a product.')) + #if not partner_id: + # raise osv.except_osv(_('No Partner!'), _('Select a partner in purchase order to choose a product.')) + #if not pricelist_id: + # raise osv.except_osv(_('No Pricelist !'), _('Select a price list in the purchase order form before choosing a product.')) # - determine name and notes based on product in partner lang. - lang = res_partner.browse(cr, uid, partner_id).lang - context_partner = {'lang': lang, 'partner_id': partner_id} + context_partner = context.copy() + if partner_id: + lang = res_partner.browse(cr, uid, partner_id).lang + context_partner.update( {'lang': lang, 'partner_id': partner_id} ) product = product_product.browse(cr, uid, product_id, context=context_partner) name = product.name if product.description_purchase: @@ -926,7 +941,7 @@ class purchase_order_line(osv.osv): qty = qty or 1.0 supplierinfo = False for supplier in product.seller_ids: - if supplier.name.id == partner_id: + if partner_id and (supplier.name.id == partner_id): supplierinfo = supplier if supplierinfo.product_uom.id != uom_id: res['warning'] = {'title': _('Warning!'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name } @@ -940,8 +955,11 @@ class purchase_order_line(osv.osv): res['value'].update({'date_planned': date_planned or dt, 'product_qty': qty}) # - determine price_unit and taxes_id - price = product_pricelist.price_get(cr, uid, [pricelist_id], - product.id, qty or 1.0, partner_id, {'uom': uom_id, 'date': date_order})[pricelist_id] + if pricelist_id: + price = product_pricelist.price_get(cr, uid, [pricelist_id], + product.id, qty or 1.0, partner_id or False, {'uom': uom_id, 'date': date_order})[pricelist_id] + else: + price = product.standard_price taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id)) fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False @@ -1111,7 +1129,7 @@ class product_template(osv.osv): _name = 'product.template' _inherit = 'product.template' _columns = { - 'purchase_ok': fields.boolean('Can be Purchased', help="Determine if the product is visible in the list of products within a selection from a purchase order line."), + 'purchase_ok': fields.boolean('Can be Purchased', help="Specify if the product can be selected in a purchase order line."), } _defaults = { 'purchase_ok': 1, diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 41be9074ba8..7e94a0b5a8e 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -226,6 +226,7 @@ + @@ -451,7 +452,7 @@ purchase.order.line.tree purchase.order.line - + @@ -471,11 +472,11 @@ purchase.order.line - +
MonTueWedThuFriSatSunTot
Week:from to From to MonTueWedThuFriSatSunTotal
Worked hours @@ -78,7 +92,7 @@ - 0 + 0h00 @@ -86,7 +100,7 @@ - 0 + 0h00 @@ -94,7 +108,7 @@ - 0 + 0h00 @@ -102,7 +116,7 @@ - 0 + 0h00 @@ -110,7 +124,7 @@ - 0 + 0h00 @@ -118,14 +132,24 @@ - 0 + 0h00
- [[ formatLang(getsumdisc(), dp='Sale Price', currency_obj = company.currency_id) ]] -
diff --git a/addons/point_of_sale/report/pos_order_report.py b/addons/point_of_sale/report/pos_order_report.py index 27086e08b26..914bef06bbf 100644 --- a/addons/point_of_sale/report/pos_order_report.py +++ b/addons/point_of_sale/report/pos_order_report.py @@ -60,7 +60,7 @@ class pos_order_report(osv.osv): to_date(to_char(s.date_order, 'dd-MM-YYYY'),'dd-MM-YYYY') as date, sum(l.qty * u.factor) as product_qty, sum(l.qty * l.price_unit) as price_total, - sum(l.qty * l.discount) as total_discount, + sum((l.qty * l.price_unit) * (l.discount / 100)) as total_discount, (sum(l.qty*l.price_unit)/sum(l.qty * u.factor))::decimal(16,2) as average_price, sum(cast(to_char(date_trunc('day',s.date_order) - date_trunc('day',s.create_date),'DD') as int)) as delay_validation, to_char(s.date_order, 'YYYY') as year, diff --git a/addons/point_of_sale/report/pos_receipt.rml b/addons/point_of_sale/report/pos_receipt.rml index d7b8b08ad29..0802114bb48 100644 --- a/addons/point_of_sale/report/pos_receipt.rml +++ b/addons/point_of_sale/report/pos_receipt.rml @@ -103,6 +103,12 @@ [[ repeatIn(o.lines,'line') ]] [[ line.product_id.name ]] + + With a [[ line and line.discount == 0.0 and removeParentNode('font') ]] ([[ '%.2f' % line.discount ]]) % discount. + + + + [[o.state=='cancel' and o.statement_ids and '-' or '']][['%.f' % line.qty ]] diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 81c6a1e1a6e..675b77345e7 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -102,6 +102,11 @@ border: 1px solid #cecbcb; border-radius: 4px; } +.point-of-sale .pos-disc-font { + font-size: 12px; + font-style:italic; + color: #808080; +} /* ********* The black header bar ********* */ diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 2c6f4e8b6e0..d7d6d3181f1 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -178,9 +178,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal [['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]] ); }).pipe(function(bank_statements){ + var journals = new Array(); + _.each(bank_statements,function(statement) { + journals.push(statement.journal_id[0]) + }); self.set('bank_statements', bank_statements); - - return self.fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]); + return self.fetch('account.journal', undefined, [['id','in', journals]]); }).pipe(function(journals){ self.set('journals',journals); @@ -462,7 +465,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var product_list = this.pos.get('product_list'); var product = this.get_product(); - var taxes_ids = product.taxes_id; + var taxes_ids = product.get('taxes_id');; var taxes = self.pos.get('taxes'); var taxtotal = 0; _.each(taxes_ids, function(el) { @@ -614,6 +617,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal return sum + orderLine.get_price_with_tax(); }), 0); }, + getDiscountTotal: function() { + return (this.get('orderLines')).reduce((function(sum, orderLine) { + return sum + (orderLine.get_list_price() * (orderLine.get_discount()/100) * orderLine.get_quantity()); + }), 0); + }, getTotalTaxExcluded: function() { return (this.get('orderLines')).reduce((function(sum, orderLine) { return sum + orderLine.get_price_without_tax(); diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index bd174b12fda..8028349b177 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -967,6 +967,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.$('#payment-paid-total').html(paidTotal.toFixed(2)); this.$('#payment-remaining').html(remaining.toFixed(2)); this.$('#payment-change').html(change.toFixed(2)); + if((currentOrder.selected_orderline == undefined)) + remaining = 1 + if(this.pos_widget.action_bar){ this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0); } diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 28ab79c65b2..ed9b1fd060a 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -202,12 +202,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.currentOrderLines.bind('remove', this.renderElement, this); }, update_numpad: function() { - var reset = false; - if (this.selected_line !== this.pos.get('selectedOrder').getSelectedLine()) { - reset = true; - } this.selected_line = this.pos.get('selectedOrder').getSelectedLine(); - if (reset && this.numpadState) + if (this.numpadState) this.numpadState.reset(); }, renderElement: function() { diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 854dd61cc1b..986921af1c5 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -541,8 +541,9 @@
+
+ Date.CultureInfo.formatPatterns.longTime)"/>


Phone:
@@ -553,6 +554,11 @@
+ +
+ With a % discount +
+
@@ -567,6 +573,9 @@
Tax:
Discount: + +
Total: