Merge lp:~jgrandguillaume-c2c/openobject-addons/multi-company-cost-price into lp:openobject-addons
- multi-company-cost-price
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 3001 |
Proposed branch: | lp:~jgrandguillaume-c2c/openobject-addons/multi-company-cost-price |
Merge into: | lp:openobject-addons |
Diff against target: |
1134 lines (+381/-161) 23 files modified
account/account_analytic_line.py (+64/-5) account/project/project.py (+3/-7) account/project/project_view.xml (+18/-8) account_analytic_analysis/account_analytic_analysis.py (+22/-37) analytic/project.py (+85/-61) analytic_user_function/analytic_user_function.py (+10/-4) hr_expense/hr_expense.py (+8/-2) hr_expense/hr_expense_view.xml (+2/-2) hr_timesheet/hr_timesheet.py (+3/-1) hr_timesheet_invoice/hr_timesheet_invoice.py (+7/-6) hr_timesheet_invoice/hr_timesheet_invoice_view.xml (+13/-1) multi_company/__init__.py (+0/-1) multi_company/__terp__.py (+1/-1) product/__init__.py (+1/-1) product/__terp__.py (+1/-0) product/company.py (+53/-0) product/company_view.xml (+18/-0) product/pricelist.py (+1/-0) product/product_data.xml (+8/-1) project_timesheet/project_timesheet.py (+12/-3) stock/stock.py (+34/-9) stock/wizard/wizard_partial_move.py (+8/-5) stock/wizard/wizard_partial_picking.py (+9/-6) |
To merge this branch: | bzr merge lp:~jgrandguillaume-c2c/openobject-addons/multi-company-cost-price |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Xavier (Open ERP) (community) | Needs Fixing | ||
Joël Grand-Guillaume @ camptocamp (community) | Needs Resubmitting | ||
Fabien (Open ERP) | Disapprove | ||
Stephane Wirtel (OpenERP) | Pending | ||
Grzegorz Grzelak (OpenGLOBE.pl) | Pending | ||
Christophe CHAUVET | Pending | ||
Raphaël Valyi - http://www.akretion.com | Pending | ||
Review via email: mp+17887@code.launchpad.net |
This proposal supersedes a proposal from 2010-01-11.
Commit message
Description of the change
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : Posted in a previous version of this proposal | # |
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote : Posted in a previous version of this proposal | # |
Hello, one thing that I can't understand is why you add a field in product and views called "standard_
Is that a typo?
If not, I think we shouldn't favor some country price by default, especially if there is just one like that. I agree that often you would need such field, but then I think you should rather add it at integration time, else is seems to me a bit arbitrary.
If that's a typo, please make sure you don't have such typo in your merge proposal.
Raphaël Valyi - http://www.akretion.com (rvalyi) : Posted in a previous version of this proposal | # |
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : Posted in a previous version of this proposal | # |
Hi !
I agree with you Raphaël. I understood that the multi_company module is only an example of multi-company config... So I add an example here to allow people understand how it should work.
Otherwise, the rest is clean. If you think I'm wrong with that, I'll remove it from multi_company module and suggest the merge again.
Personally, I think it's good to have an example, but it's ok for me to remove it.
Let me know,
Regards,
Joël
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi !
This is the merge proposal from Camptocamp to improve multi-company into OpenERP regarding to product costs in manufacturing industry and in services company.
What has been done here:
* Add price type on company as a property (with default value based on standard price)
* Stock accounting
* Use the price type currency and field for cost valuation
* Into stock move for standard price
* Into stock move for average price
* Analytic accounting
* Use the price type currency and field for cost valuation (including timesheet)
* Add multi-currency on analytic lines (similar to financial accounting)
* Allow to share the same product between company employees, with different cost for each one
* Correct all "costs" indicators into analytic account to base them on the right currency (owner's company)
* By default, nothing change for single company implementation (base the cost valuation on standard price)
* Factorise part of function field into analytic accounting
As a result, we can now really share the same product between companies that doesn't have the same currency and/or same cost price.
We can also manage one field per company on the product form to store the cost for a given price type (and so for a given company).
Refer to the blueprint for more details:
https:/
Thanks in advance for your review.
Regards,
Joël
P.S. The field standard_price_usd is an example, committed into an sample module to show how to configure this use case... It's not a typo errors...
Fabien (Open ERP) (fp-tinyerp) wrote : | # |
It covers interesting needs but can not be merged as it. Lot's of things have to be rewritten to be more generic.
Fabien (Open ERP) (fp-tinyerp) wrote : | # |
For example, this is not acceptable in the official, generic, version:
888 +class product_
889 + _inherit = "product.product"
891 + _columns = {
892 + 'standard_
893 + help="Product's cost in USDfor accounting stock valuation."),
894 + }
896 +product_product()
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi,
First, thanks for reviewing :)
Please, Raphaal already told me this the first time, but this module is (in my own understanding) a demo on how to setup multi-company !
That's why I write it the "standard_
Please, have a look closer, I'm ready to invest more time on that. It's a need my customer strongly want. We need a solution.
Could we discuss the features in their-self ? Not the demo stuff, I remove them in the minute if you want !
Let me know your advices, and I will correct what's need to be done. This way everyone win : My customer, me and OpenERP !
Thanks,
Kind regards,
Joël
Stephane Wirtel (OpenERP) (stephane-openerp) wrote : | # |
Can you remove this point from your code and add a new comment with your example in this bug report.
Thanks
- 2551. By Joël Grand-Guillaume @ camptocamp
-
[DEL] Remove the demo data in multi-company module as asked by Fabien
- 2552. By Joël Grand-Guillaume @ camptocamp
-
[DEL] Remove the multi-company view from multi-company
- 2553. By Joël Grand-Guillaume @ camptocamp
-
[MRG] Merge the last trunk in order to ease the merge from Tiny
- 2554. By Joël Grand-Guillaume @ camptocamp
-
[REF] Refactoring : moving the project.py from account to analytic module as it was done by Tiny
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi,
Well it's done ! I just remove every example fields from demo data in multicompany module. I also merge the last trunk of the day for you, so it's easier for you to apply it.
If you don't agree with one of my new features, let me know. I really invest lots of time to design those new features with my customer. I'm expecting you to consider it seriouly : if you think to refuse it again, let me know how I could help to integrate it in the core.
I mean, this is my goal, I don't want a half solution. I want this in the core of OpenERP, whatever I have to change, as long as the needed feature are implemented.
Thanks for your time and consideration,
Joël
Fabien (Open ERP) (fp-tinyerp) wrote : | # |
I checked the logic and it seems good. If someone can check the code and if it's right, you can merge.
Xavier (Open ERP) (xmo-deactivatedaccount) wrote : | # |
Fabien considers the business logic good so I have no issue with that, but on the style/tech front I have a few from a quick overview of the branch:
Inconsistency
=============
stock/wizard/
(I also note a high level of redundancy between these wizards (at least on this specific method), they might benefit from from de-duplication in the future, to avoid that kind of potential issues)
Commented code
==============
In a few places, you commented existing code or committed commented code. Please remove these instances, removed code can be found via the VCS's history, and I don't quite see the justification for added already commented code (if it's work in progress its place is in a non-merged branch, if it's something to add/complete, it should be in a bug on the tracker):
* hr_timesheet_
* account_
Style
=====
At revid:<email address hidden>, in account_
As a side note, in a few places you edited code using old-style difference operators (<>). If you can (not in this branch, but in the future) don't hesitate swapping them for "modern" difference operators (!=).
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi,
Thanks for those constructive comments !! I'll make those changes ASAP.
Little remark : those wizard are really fat, and I know that.. But I can't refactor everything at once right :) ?
I really appreciate you consider my work. I'll remember it !
Have a nice week-end,
Regards,
Xavier (Open ERP) (xmo-deactivatedaccount) wrote : | # |
> Little remark : those wizard are really fat, and I know that.. But I can't refactor everything at once right :) ?
Yeah, it was more of a tentative note for me (or anybody who reads this thing)
> I really appreciate you consider my work. I'll remember it !
Pleasure.
- 2555. By Joël Grand-Guillaume @ camptocamp
-
[FIX] According to Xavier (OpenERP) to fit OpenERP requierements : remove commented code, use != instead of <>, fix picking wizard.
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi Xavier,
I change everything according to your comments. I hope this is fine for you now.
Summary:
- Change the stocks wizards (both with amount_unit)
- Remove commented code (hope I don't miss some...)
- Change <> for !=
Note, I don't find the ".has_key" you talked about. So, in case of trouble with this, feel free to rewrite that point for the merge.
Thanks in advance for merging this !
Regards,
Joël
Xavier (Open ERP) (xmo-deactivatedaccount) wrote : | # |
Merged, the _check_currency validator threw some error when trying to install all the demo data, but if there's a bug in that it'll probably be sniffed out during usage.
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi,
Thanks a lot for that !
Best regards,
Joël
Preview Diff
1 | === modified file 'account/account_analytic_line.py' |
2 | --- account/account_analytic_line.py 2010-01-12 09:18:39 +0000 |
3 | +++ account/account_analytic_line.py 2010-02-15 10:42:23 +0000 |
4 | @@ -25,10 +25,46 @@ |
5 | from osv import osv |
6 | from tools.translate import _ |
7 | import tools |
8 | +from tools import config |
9 | |
10 | class account_analytic_line(osv.osv): |
11 | _name = 'account.analytic.line' |
12 | _description = 'Analytic lines' |
13 | + |
14 | + def _amount_currency(self, cr, uid, ids, field_name, arg, context={}): |
15 | + result = {} |
16 | + for rec in self.browse(cr, uid, ids, context): |
17 | + cmp_cur_id=rec.company_id.currency_id.id |
18 | + aa_cur_id=rec.account_id.currency_id.id |
19 | + # Always provide the amount in currency |
20 | + if cmp_cur_id != aa_cur_id: |
21 | + cur_obj = self.pool.get('res.currency') |
22 | + ctx = {} |
23 | + if rec.date and rec.amount: |
24 | + ctx['date'] = rec.date |
25 | + result[rec.id] = cur_obj.compute(cr, uid, rec.company_id.currency_id.id, |
26 | + rec.account_id.currency_id.id, rec.amount, |
27 | + context=ctx) |
28 | + else: |
29 | + result[rec.id]=rec.amount |
30 | + return result |
31 | + |
32 | + def _get_account_currency(self, cr, uid, ids, field_name, arg, context={}): |
33 | + result = {} |
34 | + for rec in self.browse(cr, uid, ids, context): |
35 | + # Always provide second currency |
36 | + result[rec.id] = (rec.account_id.currency_id.id,rec.account_id.currency_id.code) |
37 | + return result |
38 | + |
39 | + def _get_account_line(self, cr, uid, ids, context={}): |
40 | + aac_ids = {} |
41 | + for acc in self.pool.get('account.analytic.account').browse(cr, uid, ids): |
42 | + aac_ids[acc.id] = True |
43 | + aal_ids = [] |
44 | + if aac_ids: |
45 | + aal_ids = self.pool.get('account.analytic.line').search(cr, uid, [('account_id','in',aac_ids.keys())], context=context) |
46 | + return aal_ids |
47 | + |
48 | _columns = { |
49 | 'name' : fields.char('Description', size=256, required=True), |
50 | 'date' : fields.date('Date', required=True), |
51 | @@ -42,14 +78,27 @@ |
52 | 'journal_id' : fields.many2one('account.analytic.journal', 'Analytic Journal', required=True, ondelete='cascade', select=True), |
53 | 'code' : fields.char('Code', size=8), |
54 | 'user_id' : fields.many2one('res.users', 'User',), |
55 | + 'currency_id': fields.function(_get_account_currency, method=True, type='many2one', relation='res.currency', string='Account currency', |
56 | + store={ |
57 | + 'account.analytic.account': (_get_account_line, ['company_id'], 50), |
58 | + 'account.analytic.line': (lambda self,cr,uid,ids,c={}: ids, ['amount','unit_amount'],10), |
59 | + }, |
60 | + help="The related account currency if not equal to the company one."), |
61 | + 'company_id': fields.many2one('res.company','Company',required=True), |
62 | + 'amount_currency': fields.function(_amount_currency, method=True, digits=(16, int(config['price_accuracy'])), string='Amount currency', |
63 | + store={ |
64 | + 'account.analytic.account': (_get_account_line, ['company_id'], 50), |
65 | + 'account.analytic.line': (lambda self,cr,uid,ids,c={}: ids, ['amount','unit_amount'],10), |
66 | + }, |
67 | + help="The amount expressed in the related account currency if not equal to the company one."), |
68 | 'ref': fields.char('Reference', size=32), |
69 | } |
70 | _defaults = { |
71 | 'date': lambda *a: time.strftime('%Y-%m-%d'), |
72 | + 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', c), |
73 | } |
74 | _order = 'date' |
75 | |
76 | - |
77 | def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): |
78 | if context is None: |
79 | context = {} |
80 | @@ -73,11 +122,15 @@ |
81 | # (_check_company, 'You can not create analytic line that is not in the same company than the account line', ['account_id']) |
82 | ] |
83 | |
84 | - def on_change_unit_amount(self, cr, uid, id, prod_id, unit_amount, |
85 | + # Compute the cost based on the price type define into company |
86 | + # property_valuation_price_type property |
87 | + def on_change_unit_amount(self, cr, uid, id, prod_id, unit_amount,company_id, |
88 | unit=False, context=None): |
89 | + if context==None: |
90 | + context={} |
91 | uom_obj = self.pool.get('product.uom') |
92 | product_obj = self.pool.get('product.product') |
93 | -# if unit_amount and prod_id: |
94 | + company_obj=self.pool.get('res.company') |
95 | if prod_id: |
96 | prod = product_obj.browse(cr, uid, prod_id) |
97 | a = prod.product_tmpl_id.property_account_expense.id |
98 | @@ -88,8 +141,14 @@ |
99 | _('There is no expense account defined ' \ |
100 | 'for this product: "%s" (id:%d)') % \ |
101 | (prod.name, prod.id,)) |
102 | - amount = unit_amount * uom_obj._compute_price(cr, uid, |
103 | - prod.uom_id.id, prod.standard_price, unit) |
104 | + if not company_id: |
105 | + company_id=company_obj._company_default_get(cr, uid, 'account.analytic.line', context) |
106 | + |
107 | + # Compute based on pricetype |
108 | + pricetype=self.pool.get('product.price.type').browse(cr,uid,company_obj.browse(cr,uid,company_id).property_valuation_price_type.id) |
109 | + amount_unit=prod.price_get(pricetype.field, context)[prod.id] |
110 | + |
111 | + amount=amount_unit*unit_amount or 1.0 |
112 | return {'value': { |
113 | 'amount': - round(amount, 2), |
114 | 'general_account_id': a, |
115 | |
116 | === modified file 'account/project/project.py' |
117 | --- account/project/project.py 2010-02-05 15:09:27 +0000 |
118 | +++ account/project/project.py 2010-02-15 10:42:23 +0000 |
119 | @@ -1,8 +1,8 @@ |
120 | # -*- coding: utf-8 -*- |
121 | ############################################################################## |
122 | -# |
123 | +# |
124 | # OpenERP, Open Source Management Solution |
125 | -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
126 | +# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). |
127 | # |
128 | # This program is free software: you can redistribute it and/or modify |
129 | # it under the terms of the GNU Affero General Public License as |
130 | @@ -15,7 +15,7 @@ |
131 | # GNU Affero General Public License for more details. |
132 | # |
133 | # You should have received a copy of the GNU Affero General Public License |
134 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
135 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
136 | # |
137 | ############################################################################## |
138 | |
139 | @@ -25,10 +25,6 @@ |
140 | from osv import fields |
141 | from osv import osv |
142 | |
143 | -# |
144 | -# Object definition |
145 | -# |
146 | - |
147 | class account_analytic_journal(osv.osv): |
148 | _name = 'account.analytic.journal' |
149 | _columns = { |
150 | |
151 | === modified file 'account/project/project_view.xml' |
152 | --- account/project/project_view.xml 2010-01-19 07:35:47 +0000 |
153 | +++ account/project/project_view.xml 2010-02-15 10:42:23 +0000 |
154 | @@ -69,6 +69,7 @@ |
155 | <field name="parent_id" on_change="on_change_parent(parent_id)"/> |
156 | <field name="company_id" select="2" widget="selection"/> |
157 | <field name="type" select="2"/> |
158 | + <field name="company_currency_id" select="2"/> |
159 | </group> |
160 | <notebook colspan="4"> |
161 | <page string="Account Data"> |
162 | @@ -147,6 +148,9 @@ |
163 | <field name="move_id" select="2"/> |
164 | <field name="unit_amount" select="2"/> |
165 | <field name="ref" select="2"/> |
166 | + <field name="currency_id" select="2"/> |
167 | + <field name="amount_currency" select="2"/> |
168 | + <field name="company_id" select="2"/> |
169 | <newline/> |
170 | <field name="product_id" select="2"/> |
171 | <field name="product_uom_id" select="2"/> |
172 | @@ -159,16 +163,19 @@ |
173 | <field name="type">tree</field> |
174 | <field name="arch" type="xml"> |
175 | <tree editable="top" string="Analytic Entries"> |
176 | - <field name="date"/> |
177 | + <field name="date" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
178 | <field name="name"/> |
179 | - <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, product_uom_id)" sum="Total quantity"/> |
180 | - <field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, product_uom_id)"/> |
181 | + <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)" sum="Total quantity"/> |
182 | + <field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
183 | <field domain="[('type','=','normal')]" name="account_id"/> |
184 | - <field invisible="True" name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, product_uom_id)"/> |
185 | + <field invisible="True" name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
186 | <field name="amount" sum="Total amount"/> |
187 | <field name="general_account_id"/> |
188 | <field name="journal_id"/> |
189 | <field name="ref"/> |
190 | + <field name="currency_id" /> |
191 | + <field name="amount_currency" /> |
192 | + <field name="company_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
193 | </tree> |
194 | </field> |
195 | </record> |
196 | @@ -225,13 +232,16 @@ |
197 | <form string="Project line"> |
198 | <field name="name"/> |
199 | <field name="account_id"/> |
200 | - <field name="date"/> |
201 | + <field name="date" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
202 | <field name="journal_id"/> |
203 | - <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, product_uom_id)"/> |
204 | - <field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, product_uom_id)"/> |
205 | - <field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, product_uom_id)"/> |
206 | + <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
207 | + <field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
208 | + <field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
209 | <field invisible="True" name="general_account_id"/> |
210 | <field name="amount"/> |
211 | + <field name="currency_id" /> |
212 | + <field name="amount_currency" /> |
213 | + <field name="company_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)"/> |
214 | </form> |
215 | </field> |
216 | </record> |
217 | |
218 | === modified file 'account_analytic_analysis/account_analytic_analysis.py' |
219 | --- account_analytic_analysis/account_analytic_analysis.py 2010-02-01 08:29:39 +0000 |
220 | +++ account_analytic_analysis/account_analytic_analysis.py 2010-02-15 10:42:23 +0000 |
221 | @@ -34,7 +34,8 @@ |
222 | res = {} |
223 | ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
224 | if ids2: |
225 | - cr.execute("select account_analytic_line.account_id, COALESCE(sum(amount),0.0) \ |
226 | + acc_set = ",".join(map(str, ids2)) |
227 | + cr.execute("select account_analytic_line.account_id, COALESCE(sum(amount_currency),0.0) \ |
228 | from account_analytic_line \ |
229 | join account_analytic_journal \ |
230 | on account_analytic_line.journal_id = account_analytic_journal.id \ |
231 | @@ -43,15 +44,8 @@ |
232 | group by account_analytic_line.account_id" ,(ids2,)) |
233 | for account_id, sum in cr.fetchall(): |
234 | res[account_id] = round(sum,2) |
235 | - for obj_id in ids: |
236 | - res.setdefault(obj_id, 0.0) |
237 | - for child_id in self.search(cr, uid, |
238 | - [('parent_id', 'child_of', [obj_id])]): |
239 | - if child_id != obj_id: |
240 | - res[obj_id] += res.get(child_id, 0.0) |
241 | - for id in ids: |
242 | - res[id] = round(res.get(id, 0.0),2) |
243 | - return res |
244 | + |
245 | + return self._compute_currency_for_level_tree(cr, uid, ids, ids2, res, acc_set, context) |
246 | |
247 | def _ca_to_invoice_calc(self, cr, uid, ids, name, arg, context={}): |
248 | res = {} |
249 | @@ -59,6 +53,10 @@ |
250 | ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
251 | if ids2: |
252 | # Amount uninvoiced hours to invoice at sale price |
253 | + # Warnning |
254 | + # This computation doesn't take care of pricelist ! |
255 | + # Just consider list_price |
256 | + acc_set = ",".join(map(str, ids2)) |
257 | cr.execute("""SELECT account_analytic_account.id, \ |
258 | COALESCE(sum (product_template.list_price * \ |
259 | account_analytic_line.unit_amount * \ |
260 | @@ -83,17 +81,6 @@ |
261 | for account_id, sum in cr.fetchall(): |
262 | res[account_id] = round(sum,2) |
263 | |
264 | - # Expense amount and purchase invoice |
265 | - #acc_set = ",".join(map(str, ids2)) |
266 | - #cr.execute ("select account_analytic_line.account_id, sum(amount) \ |
267 | - # from account_analytic_line \ |
268 | - # join account_analytic_journal \ |
269 | - # on account_analytic_line.journal_id = account_analytic_journal.id \ |
270 | - # where account_analytic_line.account_id IN (%s) \ |
271 | - # and account_analytic_journal.type = 'purchase' \ |
272 | - # GROUP BY account_analytic_line.account_id;"%acc_set) |
273 | - #for account_id, sum in cr.fetchall(): |
274 | - # res2[account_id] = round(sum,2) |
275 | for obj_id in ids: |
276 | res.setdefault(obj_id, 0.0) |
277 | res2.setdefault(obj_id, 0.0) |
278 | @@ -160,7 +147,9 @@ |
279 | res = {} |
280 | ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
281 | if ids2: |
282 | - cr.execute("""select account_analytic_line.account_id,COALESCE(sum(amount),0.0) \ |
283 | + acc_set = ",".join(map(str, ids2)) |
284 | + cr.execute("""select account_analytic_line.account_id,COALESCE(sum(amount_currency),0.0) \ |
285 | + |
286 | from account_analytic_line \ |
287 | join account_analytic_journal \ |
288 | on account_analytic_line.journal_id = account_analytic_journal.id \ |
289 | @@ -169,20 +158,16 @@ |
290 | GROUP BY account_analytic_line.account_id""",(ids2,)) |
291 | for account_id, sum in cr.fetchall(): |
292 | res[account_id] = round(sum,2) |
293 | - for obj_id in ids: |
294 | - res.setdefault(obj_id, 0.0) |
295 | - for child_id in self.search(cr, uid, |
296 | - [('parent_id', 'child_of', [obj_id])]): |
297 | - if child_id != obj_id: |
298 | - res[obj_id] += res.get(child_id, 0.0) |
299 | - for id in ids: |
300 | - res[id] = round(res.get(id, 0.0),2) |
301 | - return res |
302 | - |
303 | + return self._compute_currency_for_level_tree(cr, uid, ids, ids2, res, acc_set, context) |
304 | + |
305 | + # TODO Take care of pricelist and purchase ! |
306 | def _ca_theorical_calc(self, cr, uid, ids, name, arg, context={}): |
307 | res = {} |
308 | res2 = {} |
309 | ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
310 | + # Warnning |
311 | + # This computation doesn't take care of pricelist ! |
312 | + # Just consider list_price |
313 | if ids2: |
314 | cr.execute("""select account_analytic_line.account_id as account_id, \ |
315 | COALESCE(sum((account_analytic_line.unit_amount * pt.list_price) \ |
316 | @@ -205,7 +190,7 @@ |
317 | GROUP BY account_analytic_line.account_id""",(ids2,)) |
318 | for account_id, sum in cr.fetchall(): |
319 | res2[account_id] = round(sum,2) |
320 | - |
321 | + |
322 | for obj_id in ids: |
323 | res.setdefault(obj_id, 0.0) |
324 | res2.setdefault(obj_id, 0.0) |
325 | @@ -214,7 +199,7 @@ |
326 | if child_id != obj_id: |
327 | res[obj_id] += res.get(child_id, 0.0) |
328 | res[obj_id] += res2.get(child_id, 0.0) |
329 | - |
330 | + |
331 | # sum both result on account_id |
332 | for id in ids: |
333 | res[id] = round(res.get(id, 0.0),2) + round(res2.get(id, 0.0),2) |
334 | @@ -289,7 +274,7 @@ |
335 | def _remaining_hours_calc(self, cr, uid, ids, name, arg, context={}): |
336 | res = {} |
337 | for account in self.browse(cr, uid, ids): |
338 | - if account.quantity_max <> 0: |
339 | + if account.quantity_max != 0: |
340 | res[account.id] = account.quantity_max - account.hours_quantity |
341 | else: |
342 | res[account.id]=0.0 |
343 | @@ -323,7 +308,7 @@ |
344 | for account in self.browse(cr, uid, ids): |
345 | if account.ca_invoiced == 0: |
346 | res[account.id]=0.0 |
347 | - elif account.total_cost <> 0.0: |
348 | + elif account.total_cost != 0.0: |
349 | res[account.id] = -(account.real_margin / account.total_cost) * 100 |
350 | else: |
351 | res[account.id] = 0.0 |
352 | @@ -334,7 +319,7 @@ |
353 | def _remaining_ca_calc(self, cr, uid, ids, name, arg, context={}): |
354 | res = {} |
355 | for account in self.browse(cr, uid, ids): |
356 | - if account.amount_max <> 0: |
357 | + if account.amount_max != 0: |
358 | res[account.id] = account.amount_max - account.ca_invoiced |
359 | else: |
360 | res[account.id]=0.0 |
361 | |
362 | === modified file 'analytic/project.py' |
363 | --- analytic/project.py 2010-02-05 15:09:27 +0000 |
364 | +++ analytic/project.py 2010-02-15 10:42:24 +0000 |
365 | @@ -33,65 +33,19 @@ |
366 | _name = 'account.analytic.account' |
367 | _description = 'Analytic Accounts' |
368 | |
369 | - def _credit_calc(self, cr, uid, ids, name, arg, context={}): |
370 | - |
371 | - where_date = '' |
372 | - if context.get('from_date',False): |
373 | - where_date += " AND l.date >= '" + context['from_date'] + "'" |
374 | - if context.get('to_date',False): |
375 | - where_date += " AND l.date <= '" + context['to_date'] + "'" |
376 | - |
377 | - cr.execute("SELECT a.id, COALESCE(SUM(l.amount),0) FROM account_analytic_account a LEFT JOIN account_analytic_line l ON (a.id=l.account_id "+where_date+") WHERE l.amount<0 and a.id =ANY(%s) GROUP BY a.id",(ids,)) |
378 | - r = dict(cr.fetchall()) |
379 | - for i in ids: |
380 | - r.setdefault(i,0.0) |
381 | - return r |
382 | - |
383 | - def _debit_calc(self, cr, uid, ids, name, arg, context={}): |
384 | - |
385 | - where_date = '' |
386 | - if context.get('from_date',False): |
387 | - where_date += " AND l.date >= '" + context['from_date'] + "'" |
388 | - if context.get('to_date',False): |
389 | - where_date += " AND l.date <= '" + context['to_date'] + "'" |
390 | - |
391 | - cr.execute("SELECT a.id, COALESCE(SUM(l.amount),0) FROM account_analytic_account a LEFT JOIN account_analytic_line l ON (a.id=l.account_id "+where_date+") WHERE l.amount>0 and a.id =ANY(%s) GROUP BY a.id" ,(ids,)) |
392 | - r= dict(cr.fetchall()) |
393 | - for i in ids: |
394 | - r.setdefault(i,0.0) |
395 | - return r |
396 | - |
397 | - def _balance_calc(self, cr, uid, ids, name, arg, context={}): |
398 | - res = {} |
399 | - ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
400 | - for i in ids: |
401 | - res.setdefault(i,0.0) |
402 | - if not ids2: |
403 | - return res |
404 | - |
405 | - where_date = '' |
406 | - if context.get('from_date',False): |
407 | - where_date += " AND l.date >= '" + context['from_date'] + "'" |
408 | - if context.get('to_date',False): |
409 | - where_date += " AND l.date <= '" + context['to_date'] + "'" |
410 | - |
411 | - cr.execute("SELECT a.id, COALESCE(SUM(l.amount),0) FROM account_analytic_account a LEFT JOIN account_analytic_line l ON (a.id=l.account_id "+where_date+") WHERE a.id =ANY(%s) GROUP BY a.id",(ids2,)) |
412 | - |
413 | - for account_id, sum in cr.fetchall(): |
414 | - res[account_id] = sum |
415 | - |
416 | - cr.execute("SELECT a.id, r.currency_id FROM account_analytic_account a INNER JOIN res_company r ON (a.company_id = r.id) where a.id =ANY(%s)",(ids2,)) |
417 | - |
418 | + def _compute_currency_for_level_tree(self, cr, uid, ids, ids2, res, acc_set, context={}): |
419 | + # Handle multi-currency on each level of analytic account |
420 | + # This is a refactoring of _balance_calc computation |
421 | + cr.execute("SELECT a.id, r.currency_id FROM account_analytic_account a INNER JOIN res_company r ON (a.company_id = r.id) where a.id in (%s)" % acc_set) |
422 | currency= dict(cr.fetchall()) |
423 | - |
424 | res_currency= self.pool.get('res.currency') |
425 | for id in ids: |
426 | if id not in ids2: |
427 | continue |
428 | for child in self.search(cr, uid, [('parent_id', 'child_of', [id])]): |
429 | - if child <> id: |
430 | + if child != id: |
431 | res.setdefault(id, 0.0) |
432 | - if currency[child]<>currency[id]: |
433 | + if currency[child]!=currency[id]: |
434 | res[id] += res_currency.compute(cr, uid, currency[child], currency[id], res.get(child, 0.0), context=context) |
435 | else: |
436 | res[id] += res.get(child, 0.0) |
437 | @@ -104,24 +58,88 @@ |
438 | |
439 | return dict([(i, res[i]) for i in ids ]) |
440 | |
441 | + |
442 | + def _credit_calc(self, cr, uid, ids, name, arg, context={}): |
443 | + res = {} |
444 | + ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
445 | + acc_set = ",".join(map(str, ids2)) |
446 | + |
447 | + for i in ids: |
448 | + res.setdefault(i,0.0) |
449 | + |
450 | + if not ids2: |
451 | + return res |
452 | + |
453 | + where_date = '' |
454 | + if context.get('from_date',False): |
455 | + where_date += " AND l.date >= '" + context['from_date'] + "'" |
456 | + if context.get('to_date',False): |
457 | + where_date += " AND l.date <= '" + context['to_date'] + "'" |
458 | + cr.execute("SELECT a.id, COALESCE(SUM(l.amount_currency),0) FROM account_analytic_account a LEFT JOIN account_analytic_line l ON (a.id=l.account_id "+where_date+") WHERE l.amount_currency<0 and a.id =ANY(%s) GROUP BY a.id",(ids2,)) |
459 | + r = dict(cr.fetchall()) |
460 | + return self._compute_currency_for_level_tree(cr, uid, ids, ids2, r, acc_set, context) |
461 | + |
462 | + def _debit_calc(self, cr, uid, ids, name, arg, context={}): |
463 | + res = {} |
464 | + ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
465 | + acc_set = ",".join(map(str, ids2)) |
466 | + |
467 | + for i in ids: |
468 | + res.setdefault(i,0.0) |
469 | + |
470 | + if not ids2: |
471 | + return res |
472 | + |
473 | + where_date = '' |
474 | + if context.get('from_date',False): |
475 | + where_date += " AND l.date >= '" + context['from_date'] + "'" |
476 | + if context.get('to_date',False): |
477 | + where_date += " AND l.date <= '" + context['to_date'] + "'" |
478 | + cr.execute("SELECT a.id, COALESCE(SUM(l.amount_currency),0) FROM account_analytic_account a LEFT JOIN account_analytic_line l ON (a.id=l.account_id "+where_date+") WHERE l.amount_currency>0 and a.id =ANY(%s) GROUP BY a.id" ,(ids2,)) |
479 | + r= dict(cr.fetchall()) |
480 | + return self._compute_currency_for_level_tree(cr, uid, ids, ids2, r, acc_set, context) |
481 | + |
482 | + def _balance_calc(self, cr, uid, ids, name, arg, context={}): |
483 | + res = {} |
484 | + ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
485 | + acc_set = ",".join(map(str, ids2)) |
486 | + |
487 | + for i in ids: |
488 | + res.setdefault(i,0.0) |
489 | + |
490 | + if not ids2: |
491 | + return res |
492 | + |
493 | + where_date = '' |
494 | + if context.get('from_date',False): |
495 | + where_date += " AND l.date >= '" + context['from_date'] + "'" |
496 | + if context.get('to_date',False): |
497 | + where_date += " AND l.date <= '" + context['to_date'] + "'" |
498 | + cr.execute("SELECT a.id, COALESCE(SUM(l.amount_currency),0) FROM account_analytic_account a LEFT JOIN account_analytic_line l ON (a.id=l.account_id "+where_date+") WHERE a.id =ANY(%s) GROUP BY a.id",(ids2,)) |
499 | + |
500 | + for account_id, sum in cr.fetchall(): |
501 | + res[account_id] = sum |
502 | + |
503 | + return self._compute_currency_for_level_tree(cr, uid, ids, ids2, res, acc_set, context) |
504 | + |
505 | def _quantity_calc(self, cr, uid, ids, name, arg, context={}): |
506 | #XXX must convert into one uom |
507 | res = {} |
508 | ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) |
509 | acc_set = ",".join(map(str, ids2)) |
510 | - |
511 | + |
512 | for i in ids: |
513 | res.setdefault(i,0.0) |
514 | - |
515 | - if not acc_set: |
516 | + |
517 | + if not ids2: |
518 | return res |
519 | - |
520 | + |
521 | where_date = '' |
522 | if context.get('from_date',False): |
523 | where_date += " AND l.date >= '" + context['from_date'] + "'" |
524 | if context.get('to_date',False): |
525 | where_date += " AND l.date <= '" + context['to_date'] + "'" |
526 | - |
527 | + |
528 | cr.execute('SELECT a.id, COALESCE(SUM(l.unit_amount), 0) \ |
529 | FROM account_analytic_account a \ |
530 | LEFT JOIN account_analytic_line l ON (a.id = l.account_id ' + where_date + ') \ |
531 | @@ -134,7 +152,7 @@ |
532 | if id not in ids2: |
533 | continue |
534 | for child in self.search(cr, uid, [('parent_id', 'child_of', [id])]): |
535 | - if child <> id: |
536 | + if child != id: |
537 | res.setdefault(id, 0.0) |
538 | res[id] += res.get(child, 0.0) |
539 | return dict([(i, res[i]) for i in ids]) |
540 | @@ -161,11 +179,15 @@ |
541 | result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code) or False |
542 | return result |
543 | |
544 | + def _get_account_currency(self, cr, uid, ids, field_name, arg, context={}): |
545 | + result=self._get_company_currency(cr, uid, ids, field_name, arg, context={}) |
546 | + return result |
547 | + |
548 | _columns = { |
549 | 'name' : fields.char('Account Name', size=128, required=True), |
550 | 'complete_name': fields.function(_complete_name_calc, method=True, type='char', string='Full Account Name'), |
551 | 'code' : fields.char('Account Code', size=24), |
552 | -# 'active' : fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the analytic account without removing it."), |
553 | + # 'active' : fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the analytic account without removing it."), |
554 | 'type': fields.selection([('view','View'), ('normal','Normal')], 'Account Type'), |
555 | 'description' : fields.text('Description'), |
556 | 'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2), |
557 | @@ -190,6 +212,7 @@ |
558 | \n* And finally when all the transactions are over, it can be in \'Close\' state. \ |
559 | \n* The project can be in either if the states \'Template\' and \'Running\'.\n If it is template then we can make projects based on the template projects. If its in \'Running\' state it is a normal project.\ |
560 | \n If it is to be reviewed then the state is \'Pending\'.\n When the project is completed the state is set to \'Done\'.'), |
561 | + 'currency_id': fields.function(_get_account_currency, method=True, type='many2one', relation='res.currency', string='Account currency', store=True), |
562 | } |
563 | |
564 | def _default_company(self, cr, uid, context={}): |
565 | @@ -198,14 +221,14 @@ |
566 | return user.company_id.id |
567 | return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0] |
568 | _defaults = { |
569 | -# 'active' : lambda *a : True, |
570 | + 'active' : lambda *a : True, |
571 | 'type' : lambda *a : 'normal', |
572 | 'company_id': _default_company, |
573 | 'state' : lambda *a : 'open', |
574 | 'user_id' : lambda self,cr,uid,ctx : uid, |
575 | 'partner_id': lambda self,cr, uid, ctx: ctx.get('partner_id', False), |
576 | 'contact_id': lambda self,cr, uid, ctx: ctx.get('contact_id', False), |
577 | - 'date_start': lambda *a: time.strftime('%Y-%m-%d') |
578 | + 'date_start': lambda *a: time.strftime('%Y-%m-%d') |
579 | } |
580 | |
581 | def check_recursion(self, cr, uid, ids, parent=None): |
582 | @@ -260,3 +283,4 @@ |
583 | return self.name_get(cr, uid, account, context=context) |
584 | |
585 | account_analytic_account() |
586 | + |
587 | |
588 | === modified file 'analytic_user_function/analytic_user_function.py' |
589 | --- analytic_user_function/analytic_user_function.py 2010-01-12 09:18:39 +0000 |
590 | +++ analytic_user_function/analytic_user_function.py 2010-02-15 10:42:25 +0000 |
591 | @@ -99,8 +99,11 @@ |
592 | _('There is no expense account define ' \ |
593 | 'for this product: "%s" (id:%d)') % \ |
594 | (r.product_id.name, r.product_id.id,)) |
595 | - amount = unit_amount * self.pool.get('product.uom')._compute_price(cr, uid, |
596 | - r.product_id.uom_id.id, r.product_id.standard_price, False) |
597 | + # Compute based on pricetype |
598 | + amount_unit=self.on_change_unit_amount(cr, uid, ids, |
599 | + r.product_id.id, unit_amount, r.product_id.uom_id.id)['value']['amount'] |
600 | + |
601 | + amount = unit_amount * amount_unit |
602 | res ['value']['amount']= - round(amount, 2) |
603 | res ['value']['general_account_id']= a |
604 | return res |
605 | @@ -132,8 +135,11 @@ |
606 | _('There is no expense account define ' \ |
607 | 'for this product: "%s" (id:%d)') % \ |
608 | (r.product_id.name, r.product_id.id,)) |
609 | - amount = unit_amount * r.product_id.uom_id._compute_price(cr, uid, |
610 | - r.product_id.uom_id.id, r.product_id.standard_price, False) |
611 | + # Compute based on pricetype |
612 | + amount_unit=self.on_change_unit_amount(cr, uid, ids, |
613 | + r.product_id.id, unit_amount, r.product_id.uom_id.id)['value']['amount'] |
614 | + |
615 | + amount = unit_amount * amount_unit |
616 | res ['value']['amount']= - round(amount, 2) |
617 | res ['value']['general_account_id']= a |
618 | return res |
619 | |
620 | === modified file 'hr_expense/hr_expense.py' |
621 | --- hr_expense/hr_expense.py 2010-02-01 08:29:39 +0000 |
622 | +++ hr_expense/hr_expense.py 2010-02-15 10:42:25 +0000 |
623 | @@ -210,12 +210,18 @@ |
624 | 'date_value' : lambda *a: time.strftime('%Y-%m-%d'), |
625 | } |
626 | _order = "sequence" |
627 | - def onchange_product_id(self, cr, uid, ids, product_id, uom_id, context={}): |
628 | + def onchange_product_id(self, cr, uid, ids, product_id, uom_id, employee_id, context={}): |
629 | v={} |
630 | if product_id: |
631 | product=self.pool.get('product.product').browse(cr,uid,product_id, context=context) |
632 | v['name']=product.name |
633 | - v['unit_amount']=product.standard_price |
634 | + |
635 | + # Compute based on pricetype of employee company |
636 | + pricetype_id = self.pool.get('hr.employee').browse(cr,uid,employee_id).user_id.company_id.property_valuation_price_type.id |
637 | + pricetype=self.pool.get('product.price.type').browse(cr,uid,pricetype_id) |
638 | + amount_unit=product.price_get(pricetype.field, context)[product.id] |
639 | + |
640 | + v['unit_amount']=amount_unit |
641 | if not uom_id: |
642 | v['uom_id']=product.uom_id.id |
643 | return {'value':v} |
644 | |
645 | === modified file 'hr_expense/hr_expense_view.xml' |
646 | --- hr_expense/hr_expense_view.xml 2010-02-01 11:09:48 +0000 |
647 | +++ hr_expense/hr_expense_view.xml 2010-02-15 10:42:25 +0000 |
648 | @@ -73,8 +73,8 @@ |
649 | <newline/> |
650 | <field colspan="4" name="line_ids" nolabel="1"> |
651 | <form string="Expense Lines"> |
652 | - <field name="product_id" on_change="onchange_product_id(product_id, uom_id)" select="2"/> |
653 | - <field name="uom_id" on_change="onchange_product_id(product_id, uom_id)" select="2"/> |
654 | + <field name="product_id" on_change="onchange_product_id(product_id, uom_id, parent.employee_id)" select="2"/> |
655 | + <field name="uom_id" on_change="onchange_product_id(product_id, uom_id, parent.employee_id)" select="2"/> |
656 | <field name="name" select="1"/> |
657 | <field name="date_value" select="1"/> |
658 | <field name="unit_quantity" select="2"/> |
659 | |
660 | === modified file 'hr_timesheet/hr_timesheet.py' |
661 | --- hr_timesheet/hr_timesheet.py 2010-01-12 09:18:39 +0000 |
662 | +++ hr_timesheet/hr_timesheet.py 2010-02-15 10:42:25 +0000 |
663 | @@ -57,7 +57,9 @@ |
664 | res = {} |
665 | # if prod_id and unit_amount: |
666 | if prod_id: |
667 | - res = self.pool.get('account.analytic.line').on_change_unit_amount(cr, uid, id, prod_id, unit_amount,unit, context) |
668 | + # find company |
669 | + company_id=self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context) |
670 | + res = self.pool.get('account.analytic.line').on_change_unit_amount(cr, uid, id, prod_id, unit_amount,company_id,unit, context) |
671 | return res |
672 | |
673 | def _getEmployeeProduct(self, cr, uid, context): |
674 | |
675 | === modified file 'hr_timesheet_invoice/hr_timesheet_invoice.py' |
676 | --- hr_timesheet_invoice/hr_timesheet_invoice.py 2010-01-21 15:13:50 +0000 |
677 | +++ hr_timesheet_invoice/hr_timesheet_invoice.py 2010-02-15 10:42:25 +0000 |
678 | @@ -52,6 +52,7 @@ |
679 | res[id] = round(res.get(id, 0.0),2) |
680 | return res |
681 | |
682 | + |
683 | _inherit = "account.analytic.account" |
684 | _columns = { |
685 | 'pricelist_id' : fields.many2one('product.pricelist', 'Sale Pricelist'), |
686 | @@ -77,7 +78,7 @@ |
687 | } |
688 | |
689 | def unlink(self, cursor, user, ids, context=None): |
690 | - self._check(cursor, user, ids) |
691 | + # self._check(cursor, user, ids) |
692 | return super(account_analytic_line,self).unlink(cursor, user, ids, |
693 | context=context) |
694 | |
695 | @@ -90,11 +91,11 @@ |
696 | select = ids |
697 | if isinstance(select, (int, long)): |
698 | select = [ids] |
699 | - if ( not vals.has_key('invoice_id')) or vals['invoice_id' ] == False: |
700 | - for line in self.browse(cr, uid, select): |
701 | - if line.invoice_id: |
702 | - raise osv.except_osv(_('Error !'), |
703 | - _('You can not modify an invoiced analytic line!')) |
704 | + if ( not vals.has_key('invoice_id')) or vals['invoice_id' ] == False: |
705 | + for line in self.browse(cr, uid, select): |
706 | + if line.invoice_id: |
707 | + raise osv.except_osv(_('Error !'), |
708 | + _('You can not modify an invoiced analytic line!')) |
709 | return True |
710 | |
711 | def copy(self, cursor, user, obj_id, default=None, context=None): |
712 | |
713 | === modified file 'hr_timesheet_invoice/hr_timesheet_invoice_view.xml' |
714 | --- hr_timesheet_invoice/hr_timesheet_invoice_view.xml 2010-01-09 14:30:02 +0000 |
715 | +++ hr_timesheet_invoice/hr_timesheet_invoice_view.xml 2010-02-15 10:42:25 +0000 |
716 | @@ -20,7 +20,19 @@ |
717 | </field> |
718 | </field> |
719 | </record> |
720 | - |
721 | + |
722 | + <record id="view_account_analytic_account_form" model="ir.ui.view"> |
723 | + <field name="name">account.analytic.account.form</field> |
724 | + <field name="model">account.analytic.account</field> |
725 | + <field name="inherit_id" ref="account.view_account_analytic_account_form"/> |
726 | + <field name="type">form</field> |
727 | + <field name="arch" type="xml"> |
728 | + <field name="company_currency_id" select="2" position="replace"> |
729 | + <field name="currency_id" select="2"/> |
730 | + </field> |
731 | + </field> |
732 | + </record> |
733 | + |
734 | <record id="hr_timesheet_line_form" model="ir.ui.view"> |
735 | <field name="name">hr.analytic.timesheet.form</field> |
736 | <field name="model">hr.analytic.timesheet</field> |
737 | |
738 | === modified file 'multi_company/__init__.py' |
739 | --- multi_company/__init__.py 2010-01-12 09:18:39 +0000 |
740 | +++ multi_company/__init__.py 2010-02-15 10:42:25 +0000 |
741 | @@ -18,6 +18,5 @@ |
742 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
743 | # |
744 | ############################################################################## |
745 | - |
746 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
747 | |
748 | |
749 | === modified file 'multi_company/__terp__.py' |
750 | --- multi_company/__terp__.py 2010-01-12 09:18:39 +0000 |
751 | +++ multi_company/__terp__.py 2010-02-15 10:42:25 +0000 |
752 | @@ -32,7 +32,7 @@ |
753 | 'depends': [ |
754 | 'base', |
755 | 'sale', |
756 | - 'project' |
757 | + 'project', |
758 | ], |
759 | 'init_xml': [], |
760 | 'update_xml': [ |
761 | |
762 | === modified file 'product/__init__.py' |
763 | --- product/__init__.py 2010-01-12 09:18:39 +0000 |
764 | +++ product/__init__.py 2010-02-15 10:42:25 +0000 |
765 | @@ -23,6 +23,6 @@ |
766 | import report |
767 | import partner |
768 | import wizard |
769 | - |
770 | +import company |
771 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
772 | |
773 | |
774 | === modified file 'product/__terp__.py' |
775 | --- product/__terp__.py 2010-01-12 09:18:39 +0000 |
776 | +++ product/__terp__.py 2010-02-15 10:42:25 +0000 |
777 | @@ -54,6 +54,7 @@ |
778 | 'product_view.xml', |
779 | 'pricelist_view.xml', |
780 | 'partner_view.xml', |
781 | + 'company_view.xml', |
782 | 'product_wizard.xml', |
783 | 'process/product_process.xml' |
784 | ], |
785 | |
786 | === added file 'product/company.py' |
787 | --- product/company.py 1970-01-01 00:00:00 +0000 |
788 | +++ product/company.py 2010-02-15 10:42:26 +0000 |
789 | @@ -0,0 +1,53 @@ |
790 | +# -*- coding: utf-8 -*- |
791 | +############################################################################## |
792 | +# |
793 | +# OpenERP, Open Source Management Solution |
794 | +# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). |
795 | +# |
796 | +# This program is free software: you can redistribute it and/or modify |
797 | +# it under the terms of the GNU Affero General Public License as |
798 | +# published by the Free Software Foundation, either version 3 of the |
799 | +# License, or (at your option) any later version. |
800 | +# |
801 | +# This program is distributed in the hope that it will be useful, |
802 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
803 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
804 | +# GNU Affero General Public License for more details. |
805 | +# |
806 | +# You should have received a copy of the GNU Affero General Public License |
807 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
808 | +# |
809 | +############################################################################## |
810 | + |
811 | +from osv import fields, osv |
812 | + |
813 | + |
814 | +class res_company(osv.osv): |
815 | + _inherit = 'res.company' |
816 | + _columns = { |
817 | + 'property_valuation_price_type': fields.property( |
818 | + 'product.price.type', |
819 | + type='many2one', |
820 | + relation='product.price.type', |
821 | + domain=[], |
822 | + string="Valuation Price Type", |
823 | + method=True, |
824 | + view_load=True, |
825 | + help="The price type field in the selected price type will be used, instead of the default one, \ |
826 | + for valuation of product in the current company"), |
827 | + } |
828 | + |
829 | + def _check_currency(self, cr, uid, ids): |
830 | + for rec in self.browse(cr, uid, ids): |
831 | + if rec.currency_id.id <> rec.property_valuation_price_type.currency_id.id: |
832 | + return False |
833 | + return True |
834 | + |
835 | + _constraints = [ |
836 | + (_check_currency, 'Error! You can not chooes a pricetype in a different currency than your company (Not supported now).', ['property_valuation_price_type']) |
837 | + ] |
838 | + |
839 | +res_company() |
840 | + |
841 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
842 | + |
843 | |
844 | === added file 'product/company_view.xml' |
845 | --- product/company_view.xml 1970-01-01 00:00:00 +0000 |
846 | +++ product/company_view.xml 2010-02-15 10:42:26 +0000 |
847 | @@ -0,0 +1,18 @@ |
848 | +<openerp> |
849 | + <data> |
850 | + |
851 | + |
852 | + <record id="view_company_property_form" model="ir.ui.view"> |
853 | + <field name="name">res.company.product.property.form.inherit</field> |
854 | + <field name="model">res.company</field> |
855 | + <field name="type">form</field> |
856 | + <field name="inherit_id" ref="base.view_company_form"/> |
857 | + <field name="arch" type="xml"> |
858 | + <field name="currency_id" position="after"> |
859 | + <field name="property_valuation_price_type"/> |
860 | + </field> |
861 | + </field> |
862 | + </record> |
863 | + |
864 | + </data> |
865 | +</openerp> |
866 | |
867 | === modified file 'product/pricelist.py' |
868 | --- product/pricelist.py 2010-02-01 08:29:39 +0000 |
869 | +++ product/pricelist.py 2010-02-15 10:42:26 +0000 |
870 | @@ -62,6 +62,7 @@ |
871 | "active": lambda *args: True, |
872 | "currency_id": _get_currency |
873 | } |
874 | + |
875 | price_type() |
876 | |
877 | #---------------------------------------------------------- |
878 | |
879 | === modified file 'product/product_data.xml' |
880 | --- product/product_data.xml 2009-09-24 10:46:21 +0000 |
881 | +++ product/product_data.xml 2010-02-15 10:42:26 +0000 |
882 | @@ -73,7 +73,6 @@ |
883 | |
884 | |
885 | |
886 | - |
887 | <!-- |
888 | Price list type |
889 | --> |
890 | @@ -81,6 +80,7 @@ |
891 | <field name="name">Sale Pricelist</field> |
892 | <field name="key">sale</field> |
893 | </record> |
894 | + |
895 | |
896 | <!-- |
897 | Price list |
898 | @@ -107,5 +107,12 @@ |
899 | <field name="fields_id" search="[('model','=','res.partner'),('name','=','property_product_pricelist')]"/> |
900 | <field eval="'product.pricelist,'+str(ref('list0'))" name="value"/> |
901 | </record> |
902 | + |
903 | + <record forcecreate="True" id="property_valuation_price_type" model="ir.property"> |
904 | + <field name="name">property_valuation_price_type</field> |
905 | + <field name="fields_id" search="[('model','=','res.company'),('name','=','property_valuation_price_type')]"/> |
906 | + <field eval="'product.price.type,'+str(ref('standard_price'))" name="value"/> |
907 | + </record> |
908 | + |
909 | </data> |
910 | </openerp> |
911 | |
912 | === modified file 'project_timesheet/project_timesheet.py' |
913 | --- project_timesheet/project_timesheet.py 2010-01-21 15:13:50 +0000 |
914 | +++ project_timesheet/project_timesheet.py 2010-02-15 10:42:26 +0000 |
915 | @@ -77,8 +77,12 @@ |
916 | vals_line['journal_id'] = result['journal_id'] |
917 | vals_line['amount'] = 00.0 |
918 | timeline_id = obj.create(cr, uid, vals_line, {}) |
919 | - |
920 | - vals_line['amount'] = (-1) * vals['hours']* ( obj.browse(cr,uid,timeline_id).product_id.standard_price or 0.0) |
921 | + |
922 | + # Compute based on pricetype |
923 | + amount_unit=obj.on_change_unit_amount(cr, uid, line_id, |
924 | + vals_line['product_id'], vals_line['unit_amount'], unit, context) |
925 | + |
926 | + vals_line['amount'] = (-1) * vals['hours']* (unit_amount or 0.0) |
927 | obj.write(cr, uid,[timeline_id], vals_line, {}) |
928 | vals['hr_analytic_timesheet_id'] = timeline_id |
929 | return super(project_work,self).create(cr, uid, vals, *args, **kwargs) |
930 | @@ -104,7 +108,12 @@ |
931 | vals_line['date'] = vals['date'][:10] |
932 | if 'hours' in vals: |
933 | vals_line['unit_amount'] = vals['hours'] |
934 | - vals_line['amount'] = (-1) * vals['hours'] * (obj.browse(cr,uid,line_id).product_id.standard_price or 0.0) |
935 | + |
936 | + # Compute based on pricetype |
937 | + amount_unit=obj.on_change_unit_amount(cr, uid, line_id, |
938 | + vals_line['product_id'], vals_line['unit_amount'], unit, context) |
939 | + |
940 | + vals_line['amount'] = (-1) * vals['hours'] * (amount_unit or 0.0) |
941 | obj.write(cr, uid, [line_id], vals_line, {}) |
942 | |
943 | return super(project_work,self).write(cr, uid, ids, vals, context) |
944 | |
945 | === modified file 'stock/stock.py' |
946 | --- stock/stock.py 2010-02-09 08:31:46 +0000 |
947 | +++ stock/stock.py 2010-02-15 10:42:27 +0000 |
948 | @@ -110,11 +110,15 @@ |
949 | cr.execute('select distinct product_id from stock_move where (location_id=%s) or (location_dest_id=%s)', (id, id)) |
950 | result = cr.dictfetchall() |
951 | if result: |
952 | + # Choose the right filed standard_price to read |
953 | + # Take the user company |
954 | + price_type_id=self.pool.get('res.users').browse(cr,uid,uid).company_id.property_valuation_price_type.id |
955 | + pricetype=self.pool.get('product.price.type').browse(cr,uid,price_type_id) |
956 | for r in result: |
957 | c = (context or {}).copy() |
958 | c['location'] = id |
959 | - product = self.pool.get('product.product').read(cr, uid, r['product_id'], [field_to_read, 'standard_price'], context=c) |
960 | - final_value += (product[field_to_read] * product['standard_price']) |
961 | + product = self.pool.get('product.product').read(cr, uid, r['product_id'], [field_to_read, pricetype.field], context=c) |
962 | + final_value += (product[field_to_read] * product[pricetype.field]) |
963 | return final_value |
964 | |
965 | def _product_value(self, cr, uid, ids, field_names, arg, context={}): |
966 | @@ -211,6 +215,10 @@ |
967 | if context is None: |
968 | context = {} |
969 | product_obj = self.pool.get('product.product') |
970 | + # Take the user company and pricetype |
971 | + price_type_id=self.pool.get('res.users').browse(cr,uid,uid).company_id.property_valuation_price_type.id |
972 | + pricetype=self.pool.get('product.price.type').browse(cr,uid,price_type_id) |
973 | + |
974 | if not product_ids: |
975 | product_ids = product_obj.search(cr, uid, []) |
976 | |
977 | @@ -241,10 +249,16 @@ |
978 | continue |
979 | product = products_by_id[product_id] |
980 | quantity_total += qty[product_id] |
981 | - price = qty[product_id] * product.standard_price |
982 | + |
983 | + # Compute based on pricetype |
984 | + # Choose the right filed standard_price to read |
985 | + amount_unit=product.price_get(pricetype.field, context)[product.id] |
986 | + price = qty[product_id] * amount_unit |
987 | + # price = qty[product_id] * product.standard_price |
988 | + |
989 | total_price += price |
990 | result['product'].append({ |
991 | - 'price': product.standard_price, |
992 | + 'price': amount_unit, |
993 | 'prod_name': product.name, |
994 | 'code': product.default_code, # used by lot_overview_all report! |
995 | 'variants': product.variants or '', |
996 | @@ -645,7 +659,11 @@ |
997 | def _get_price_unit_invoice(self, cursor, user, move_line, type): |
998 | '''Return the price unit for the move line''' |
999 | if type in ('in_invoice', 'in_refund'): |
1000 | - return move_line.product_id.standard_price |
1001 | + # Take the user company and pricetype |
1002 | + price_type_id=self.pool.get('res.users').browse(cr,users,users).company_id.property_valuation_price_type.id |
1003 | + pricetype=self.pool.get('product.price.type').browse(cr,uid,price_type_id) |
1004 | + amount_unit=move_line.product_id.price_get(pricetype.field, context)[move_line.product_id.id] |
1005 | + return amount_unit |
1006 | else: |
1007 | return move_line.product_id.list_price |
1008 | |
1009 | @@ -1408,13 +1426,19 @@ |
1010 | ref = move.picking_id and move.picking_id.name or False |
1011 | product_uom_obj = self.pool.get('product.uom') |
1012 | default_uom = move.product_id.uom_id.id |
1013 | + date = time.strftime('%Y-%m-%d') |
1014 | q = product_uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, default_uom) |
1015 | if move.product_id.cost_method == 'average' and move.price_unit: |
1016 | amount = q * move.price_unit |
1017 | + # Base computation on valuation price type |
1018 | else: |
1019 | - amount = q * move.product_id.standard_price |
1020 | - |
1021 | - date = time.strftime('%Y-%m-%d') |
1022 | + company_id=move.company_id.id |
1023 | + |
1024 | + pricetype=self.pool.get('product.price.type').browse(cr,uid,move.company_id.property_valuation_price_type.id) |
1025 | + amount_unit=move.product_id.price_get(pricetype.field, context)[move.product_id.id] |
1026 | + amount=amount_unit * q or 1.0 |
1027 | + # amount = q * move.product_id.standard_price |
1028 | + |
1029 | partner_id = False |
1030 | if move.picking_id: |
1031 | partner_id = move.picking_id.address_id and (move.picking_id.address_id.partner_id and move.picking_id.address_id.partner_id.id or False) or False |
1032 | @@ -1492,7 +1516,8 @@ |
1033 | move_line = [] |
1034 | for line in inv.inventory_line_id: |
1035 | pid = line.product_id.id |
1036 | - price = line.product_id.standard_price or 0.0 |
1037 | + |
1038 | + # price = line.product_id.standard_price or 0.0 |
1039 | amount = self.pool.get('stock.location')._product_get(cr, uid, line.location_id.id, [pid], {'uom': line.product_uom.id})[pid] |
1040 | change = line.product_qty - amount |
1041 | lot_id = line.prod_lot_id.id |
1042 | |
1043 | === modified file 'stock/wizard/wizard_partial_move.py' |
1044 | --- stock/wizard/wizard_partial_move.py 2010-01-12 09:18:39 +0000 |
1045 | +++ stock/wizard/wizard_partial_move.py 2010-02-15 10:42:27 +0000 |
1046 | @@ -55,7 +55,7 @@ |
1047 | |
1048 | for move in move_lines: |
1049 | quantity = move.product_qty |
1050 | - if move.state <> 'assigned': |
1051 | + if move.state != 'assigned': |
1052 | quantity = 0 |
1053 | |
1054 | _moves_arch_lst.append('<field name="move%s" />' % (move.id,)) |
1055 | @@ -124,7 +124,8 @@ |
1056 | currency = data['form']['currency%s' % move.id] |
1057 | |
1058 | qty = uom_obj._compute_qty(cr, uid, uom, qty, product.uom_id.id) |
1059 | - |
1060 | + pricetype=pool.get('product.price.type').browse(cr,uid,user.company_id.property_valuation_price_type.id) |
1061 | + |
1062 | if (qty > 0): |
1063 | new_price = currency_obj.compute(cr, uid, currency, |
1064 | user.company_id.currency_id.id, price) |
1065 | @@ -133,15 +134,17 @@ |
1066 | if product.qty_available<=0: |
1067 | new_std_price = new_price |
1068 | else: |
1069 | - new_std_price = ((product.standard_price * product.qty_available)\ |
1070 | + # Get the standard price |
1071 | + amount_unit=product.price_get(pricetype.field, context)[product.id] |
1072 | + new_std_price = ((amount_unit * product.qty_available)\ |
1073 | + (new_price * qty))/(product.qty_available + qty) |
1074 | |
1075 | product_obj.write(cr, uid, [product.id], |
1076 | - {'standard_price': new_std_price}) |
1077 | + {pricetype.field: new_std_price}) |
1078 | move_obj.write(cr, uid, move.id, {'price_unit': new_price}) |
1079 | |
1080 | for move in too_few: |
1081 | - if data['form']['move%s' % move.id] <> 0: |
1082 | + if data['form']['move%s' % move.id] != 0: |
1083 | new_move = move_obj.copy(cr, uid, move.id, |
1084 | { |
1085 | 'product_qty' : data['form']['move%s' % move.id], |
1086 | |
1087 | === modified file 'stock/wizard/wizard_partial_picking.py' |
1088 | --- stock/wizard/wizard_partial_picking.py 2010-01-21 15:13:50 +0000 |
1089 | +++ stock/wizard/wizard_partial_picking.py 2010-02-15 10:42:27 +0000 |
1090 | @@ -61,7 +61,7 @@ |
1091 | if m.state in ('done', 'cancel'): |
1092 | continue |
1093 | quantity = m.product_qty |
1094 | - if m.state<>'assigned': |
1095 | + if m.state!='assigned': |
1096 | quantity = 0 |
1097 | |
1098 | _moves_arch_lst.append('<field name="move%s" />' % (m.id,)) |
1099 | @@ -133,7 +133,7 @@ |
1100 | currency = data['form']['currency%s' % move.id] |
1101 | |
1102 | qty = uom_obj._compute_qty(cr, uid, uom, qty, product.uom_id.id) |
1103 | - |
1104 | + pricetype=pool.get('product.price.type').browse(cr,uid,user.company_id.property_valuation_price_type.id) |
1105 | if (qty > 0): |
1106 | new_price = currency_obj.compute(cr, uid, currency, |
1107 | user.company_id.currency_id.id, price) |
1108 | @@ -142,11 +142,14 @@ |
1109 | if product.qty_available<=0: |
1110 | new_std_price = new_price |
1111 | else: |
1112 | - new_std_price = ((product.standard_price * product.qty_available)\ |
1113 | + # Get the standard price |
1114 | + amount_unit=product.price_get(pricetype.field, context)[product.id] |
1115 | + new_std_price = ((amount_unit * product.qty_available)\ |
1116 | + (new_price * qty))/(product.qty_available + qty) |
1117 | - |
1118 | + |
1119 | + # Write the field according to price type field |
1120 | product_obj.write(cr, uid, [product.id], |
1121 | - {'standard_price': new_std_price}) |
1122 | + {pricetype.field: new_std_price}) |
1123 | move_obj.write(cr, uid, [move.id], {'price_unit': new_price}) |
1124 | |
1125 | for move in too_few: |
1126 | @@ -158,7 +161,7 @@ |
1127 | 'move_lines' : [], |
1128 | 'state':'draft', |
1129 | }) |
1130 | - if data['form']['move%s' % move.id] <> 0: |
1131 | + if data['form']['move%s' % move.id] != 0: |
1132 | new_obj = move_obj.copy(cr, uid, move.id, |
1133 | { |
1134 | 'product_qty' : data['form']['move%s' % move.id], |
Hi !
This is the merge proposal from Camptocamp to improve multi-company into OpenERP regarding to product costs in manufacturing industry and in services company.
What has been done here:
* Add price type on company as a property (with default value based on standard price)
* Stock accounting
* Use the price type currency and field for cost valuation
* Into stock move for standard price
* Into stock move for average price
* Analytic accounting
* Use the price type currency and field for cost valuation (including timesheet)
* Add multi-currency on analytic lines (similar to financial accounting)
* Allow to share the same product between company employees, with different cost for each one
* Correct all "costs" indicators into analytic account to base them on the right currency (owner's company)
* By default, nothing change for single company implementation (base the cost valuation on standard price)
As a result, we can now really share the same product between companies that doesn't have the same currency and/or same cost price.
We can also manage one field per company on the product form to store the cost for a given price type (and so for a given company).
Refer to the blueprint for more details:
https:/ /blueprints. launchpad. net/openobject- addons/ +spec/multi- cost-price- and-product- currency
Thanks in advance for your review.
Regards,
Joël