Merge lp:~openbig/bigconsulting/account_payment_discount_extension into lp:bigconsulting
- account_payment_discount_extension
- Merge into addons
Proposed by
gpa(OpenERP)
Status: | Merged |
---|---|
Merged at revision: | 80 |
Proposed branch: | lp:~openbig/bigconsulting/account_payment_discount_extension |
Merge into: | lp:bigconsulting |
Diff against target: |
487 lines (+449/-0) 7 files modified
account_payment_discount_extension/__init__.py (+24/-0) account_payment_discount_extension/__terp__.py (+42/-0) account_payment_discount_extension/account_payment_discount.py (+62/-0) account_payment_discount_extension/account_payment_discount_view.xml (+85/-0) account_payment_discount_extension/account_payment_disocunt_wizard.xml (+6/-0) account_payment_discount_extension/wizard/__init__.py (+26/-0) account_payment_discount_extension/wizard/wizard_payment_order1.py (+204/-0) |
To merge this branch: | bzr merge lp:~openbig/bigconsulting/account_payment_discount_extension |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
openbig | Pending | ||
Review via email: mp+33354@code.launchpad.net |
This proposal supersedes a proposal from 2010-08-20.
Commit message
Description of the change
added new module account_
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'account_payment_discount_extension' |
2 | === added file 'account_payment_discount_extension/__init__.py' |
3 | --- account_payment_discount_extension/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ account_payment_discount_extension/__init__.py 2010-08-23 07:30:56 +0000 |
5 | @@ -0,0 +1,24 @@ |
6 | +# -*- encoding: utf-8 -*- |
7 | +############################################################################## |
8 | +# |
9 | +# OpenERP, Open Source Management Solution |
10 | +# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved |
11 | +# $Id$ |
12 | +# |
13 | +# This program is free software: you can redistribute it and/or modify |
14 | +# it under the terms of the GNU General Public License as published by |
15 | +# the Free Software Foundation, either version 3 of the License, or |
16 | +# (at your option) any later version. |
17 | +# |
18 | +# This program is distributed in the hope that it will be useful, |
19 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
21 | +# GNU General Public License for more details. |
22 | +# |
23 | +# You should have received a copy of the GNU General Public License |
24 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
25 | +# |
26 | +############################################################################## |
27 | + |
28 | +import account_payment_discount |
29 | +import wizard |
30 | \ No newline at end of file |
31 | |
32 | === added file 'account_payment_discount_extension/__terp__.py' |
33 | --- account_payment_discount_extension/__terp__.py 1970-01-01 00:00:00 +0000 |
34 | +++ account_payment_discount_extension/__terp__.py 2010-08-23 07:30:56 +0000 |
35 | @@ -0,0 +1,42 @@ |
36 | +# -*- encoding: utf-8 -*- |
37 | +############################################################################## |
38 | +# |
39 | +# OpenERP, Open Source Management Solution |
40 | +# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved |
41 | +# $Id$ |
42 | +# |
43 | +# This program is free software: you can redistribute it and/or modify |
44 | +# it under the terms of the GNU General Public License as published by |
45 | +# the Free Software Foundation, either version 3 of the License, or |
46 | +# (at your option) any later version. |
47 | +# |
48 | +# This program is distributed in the hope that it will be useful, |
49 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
50 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
51 | +# GNU General Public License for more details. |
52 | +# |
53 | +# You should have received a copy of the GNU General Public License |
54 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
55 | +# |
56 | +############################################################################## |
57 | + |
58 | +{ |
59 | + "name" : "Account Payment Discount Extension", |
60 | + "version" : "1.1", |
61 | + "author" : "Big Consulting", |
62 | + "website" : "http://www.openbig.org", |
63 | + "category" : "Generic Modules/Accounting", |
64 | + "license" : "GPL-3", |
65 | + "depends" : ["base","account","account_invoice_cash_discount","account_payment_extension",], |
66 | + "init_xml" : [ |
67 | + ], |
68 | + "demo_xml" : [ |
69 | + ], |
70 | + "update_xml" : [ |
71 | + "account_payment_disocunt_wizard.xml", |
72 | + "account_payment_discount_view.xml", |
73 | + ], |
74 | + "active": False, |
75 | + "installable": True, |
76 | +} |
77 | + |
78 | |
79 | === added file 'account_payment_discount_extension/account_payment_discount.py' |
80 | --- account_payment_discount_extension/account_payment_discount.py 1970-01-01 00:00:00 +0000 |
81 | +++ account_payment_discount_extension/account_payment_discount.py 2010-08-23 07:30:56 +0000 |
82 | @@ -0,0 +1,62 @@ |
83 | +# -*- encoding: utf-8 -*- |
84 | +############################################################################## |
85 | +# |
86 | +# OpenERP, Open Source Management Solution |
87 | +# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved |
88 | +# $Id$ |
89 | +# |
90 | +# This program is free software: you can redistribute it and/or modify |
91 | +# it under the terms of the GNU General Public License as published by |
92 | +# the Free Software Foundation, either version 3 of the License, or |
93 | +# (at your option) any later version. |
94 | +# |
95 | +# This program is distributed in the hope that it will be useful, |
96 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
97 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
98 | +# GNU General Public License for more details. |
99 | +# |
100 | +# You should have received a copy of the GNU General Public License |
101 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
102 | +# |
103 | +############################################################################## |
104 | + |
105 | +import netsvc |
106 | +from osv import fields, osv |
107 | +import time |
108 | +from datetime import datetime |
109 | +from dateutil.relativedelta import relativedelta |
110 | + |
111 | + |
112 | +class payment_order(osv.osv): |
113 | + _inherit='payment.order' |
114 | + _columns={ |
115 | + 'inter_bank_journal': fields.many2one('account.journal', 'Intermediate Band Journal', required=False), |
116 | + } |
117 | +payment_order() |
118 | + |
119 | +class account_invoice(osv.osv): |
120 | + _inherit='account.invoice' |
121 | + _columns={ |
122 | + 'next_payment_date': fields.date('Next Payment Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},), |
123 | + } |
124 | + def action_date_assign(self, cr, uid, ids, *args): |
125 | + data = super(account_invoice,self).action_date_assign(cr, uid, ids, *args) |
126 | + for inv in self.browse(cr, uid, ids): |
127 | + if inv.payment_term.id: |
128 | + payment_data = self.pool.get('account.payment.term').browse(cr, uid, inv.payment_term.id).cash_discount_ids |
129 | + if payment_data: |
130 | + delay = payment_data[0].delay |
131 | + self.write(cr, uid, [inv.id], {'next_payment_date':(datetime.now() + relativedelta(days=delay)).strftime('%Y-%m-%d')}) |
132 | + return data |
133 | + |
134 | +account_invoice() |
135 | + |
136 | +class payment_line(osv.osv): |
137 | + _name = 'payment.line' |
138 | + _inherit = 'payment.line' |
139 | + _columns = { |
140 | + 'cash_discount': fields.float('Cash Discount'), |
141 | + 'discount_date': fields.date('Discount Date'), |
142 | + 'pay_amount': fields.float('Pay Amount'), |
143 | + } |
144 | +payment_line() |
145 | \ No newline at end of file |
146 | |
147 | === added file 'account_payment_discount_extension/account_payment_discount_view.xml' |
148 | --- account_payment_discount_extension/account_payment_discount_view.xml 1970-01-01 00:00:00 +0000 |
149 | +++ account_payment_discount_extension/account_payment_discount_view.xml 2010-08-23 07:30:56 +0000 |
150 | @@ -0,0 +1,85 @@ |
151 | +<?xml version="1.0" encoding="utf-8"?> |
152 | +<openerp> |
153 | + <data> |
154 | + |
155 | + <record model="ir.ui.view" id="invoice_customer_form_inherit11"> |
156 | + <field name="name">account.invoice.customer.form.inherit</field> |
157 | + <field name="model">account.invoice</field> |
158 | + <field name="type">form</field> |
159 | + <field name="inherit_id" ref="account.invoice_form"/> |
160 | + <field name="arch" type="xml"> |
161 | + <field name="date_due" position="after"> |
162 | + <field name="next_payment_date" /> |
163 | + </field> |
164 | + </field> |
165 | + </record> |
166 | + |
167 | + <record model="ir.ui.view" id="invoice_supplier_form_inherit11"> |
168 | + <field name="name">account.invoice.supplier.form.inherit</field> |
169 | + <field name="model">account.invoice</field> |
170 | + <field name="type">form</field> |
171 | + <field name="inherit_id" ref="account.invoice_supplier_form"/> |
172 | + <field name="priority">2</field> |
173 | + <field name="arch" type="xml"> |
174 | + <field name="number" position="before"> |
175 | + <field name="next_payment_date" /> |
176 | + </field> |
177 | + </field> |
178 | + </record> |
179 | + |
180 | + <record id="view_payment_order_form_wizard_ext" model="ir.ui.view"> |
181 | + <field name="name">payment.order.form.ext11</field> |
182 | + <field name="model">payment.order</field> |
183 | + <field name="type">form</field> |
184 | + <field name="inherit_id" ref="account_payment_extension.view_payment_order_form_ext1"/> |
185 | + <field name="arch" type="xml"> |
186 | + <xpath expr="//button[@string='Select invoices to pay/receive payment']" position="replace"> |
187 | + <button name="%(wizard_populate_payment_ext111)d" string="Select invoices to pay/receive payment" type="action" attrs="{'invisible':[('state','=','done')]}"/> |
188 | + </xpath> |
189 | + </field> |
190 | + </record> |
191 | + |
192 | + <record model="ir.ui.view" id="view_payment_order_form1"> |
193 | + <field name="name">payment.order.form1</field> |
194 | + <field name="model">payment.order</field> |
195 | + <field name="inherit_id" ref="account_payment.view_payment_order_form"/> |
196 | + <field name="type">form</field> |
197 | + <field name="arch" type="xml"> |
198 | + <xpath expr="//field[@name='partner_id']" position="before"> |
199 | + <field name="cash_discount" /> |
200 | + <field name="discount_date" /> |
201 | + <field name="pay_amount" /> |
202 | + <label stirng=""/> |
203 | + </xpath> |
204 | + </field> |
205 | + </record> |
206 | + |
207 | + <record model="ir.ui.view" id="view_payment_order_tree1"> |
208 | + <field name="name">payment.order.form1</field> |
209 | + <field name="model">payment.order</field> |
210 | + <field name="inherit_id" ref="account_payment.view_payment_order_form"/> |
211 | + <field name="type">form</field> |
212 | + <field name="arch" type="xml"> |
213 | + <xpath expr="//tree/field[@name='amount']" position="after"> |
214 | + <field name="cash_discount" /> |
215 | + <field name="discount_date" /> |
216 | + <field name="pay_amount" /> |
217 | + <label stirng=""/> |
218 | + </xpath> |
219 | + </field> |
220 | + </record> |
221 | + |
222 | + <record model="ir.ui.view" id="view_payment_order_bank_form1"> |
223 | + <field name="name">payment.order.form1</field> |
224 | + <field name="model">payment.order</field> |
225 | + <field name="inherit_id" ref="account_payment.view_payment_order_form"/> |
226 | + <field name="type">form</field> |
227 | + <field name="arch" type="xml"> |
228 | + <field name="user_id" position="after"> |
229 | + <field name="inter_bank_journal" required="True"/> |
230 | + </field> |
231 | + </field> |
232 | + </record> |
233 | + |
234 | + </data> |
235 | +</openerp> |
236 | |
237 | === added file 'account_payment_discount_extension/account_payment_disocunt_wizard.xml' |
238 | --- account_payment_discount_extension/account_payment_disocunt_wizard.xml 1970-01-01 00:00:00 +0000 |
239 | +++ account_payment_discount_extension/account_payment_disocunt_wizard.xml 2010-08-23 07:30:56 +0000 |
240 | @@ -0,0 +1,6 @@ |
241 | +<?xml version="1.0" encoding="utf-8"?> |
242 | +<openerp> |
243 | + <data> |
244 | + <wizard id="wizard_populate_payment_ext111" menu="True" model="payment.order" name="populate_payment_ext111" string="Populate payment to pay2222"/> |
245 | + </data> |
246 | +</openerp> |
247 | |
248 | === added directory 'account_payment_discount_extension/wizard' |
249 | === added file 'account_payment_discount_extension/wizard/__init__.py' |
250 | --- account_payment_discount_extension/wizard/__init__.py 1970-01-01 00:00:00 +0000 |
251 | +++ account_payment_discount_extension/wizard/__init__.py 2010-08-23 07:30:56 +0000 |
252 | @@ -0,0 +1,26 @@ |
253 | +# -*- encoding: utf-8 -*- |
254 | +############################################################################## |
255 | +# |
256 | +# OpenERP, Open Source Management Solution |
257 | +# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved |
258 | +# $Id$ |
259 | +# |
260 | +# This program is free software: you can redistribute it and/or modify |
261 | +# it under the terms of the GNU General Public License as published by |
262 | +# the Free Software Foundation, either version 3 of the License, or |
263 | +# (at your option) any later version. |
264 | +# |
265 | +# This program is distributed in the hope that it will be useful, |
266 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
267 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
268 | +# GNU General Public License for more details. |
269 | +# |
270 | +# You should have received a copy of the GNU General Public License |
271 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
272 | +# |
273 | +############################################################################## |
274 | + |
275 | +import wizard_payment_order1 |
276 | + |
277 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
278 | + |
279 | |
280 | === added file 'account_payment_discount_extension/wizard/wizard_payment_order1.py' |
281 | --- account_payment_discount_extension/wizard/wizard_payment_order1.py 1970-01-01 00:00:00 +0000 |
282 | +++ account_payment_discount_extension/wizard/wizard_payment_order1.py 2010-08-23 07:30:56 +0000 |
283 | @@ -0,0 +1,204 @@ |
284 | +# -*- encoding: utf-8 -*- |
285 | +############################################################################## |
286 | +# |
287 | +# OpenERP, Open Source Management Solution |
288 | +# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved |
289 | +# $Id$ |
290 | +# |
291 | +# This program is free software: you can redistribute it and/or modify |
292 | +# it under the terms of the GNU General Public License as published by |
293 | +# the Free Software Foundation, either version 3 of the License, or |
294 | +# (at your option) any later version. |
295 | +# |
296 | +# This program is distributed in the hope that it will be useful, |
297 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
298 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
299 | +# GNU General Public License for more details. |
300 | +# |
301 | +# You should have received a copy of the GNU General Public License |
302 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
303 | +# |
304 | +############################################################################## |
305 | + |
306 | +import wizard |
307 | +import pooler |
308 | +from tools.misc import UpdateableStr |
309 | +import time |
310 | + |
311 | + |
312 | +FORM = UpdateableStr() |
313 | + |
314 | +FIELDS = { |
315 | + 'entries': {'string':'Entries', 'type':'many2many', 'relation':'account.move.line',}, |
316 | + 'communication2': {'string':'Communication 2', 'type':'char', 'size': 64, 'help':'The successor message of payment communication.'}, |
317 | +} |
318 | + |
319 | +field_duedate={ |
320 | + 'duedate': {'string':'Due Date', 'type':'date','required':True, 'default': lambda *a: time.strftime('%Y-%m-%d'),}, |
321 | + 'amount': {'string':'Amount', 'type':'float', 'help': 'Next step will automatically select payments up to this amount.'} |
322 | + } |
323 | +arch_duedate='''<?xml version="1.0" encoding="utf-8"?> |
324 | +<form string="Search Payment lines" col="2"> |
325 | + <field name="duedate" /> |
326 | + <field name="amount" /> |
327 | +</form>''' |
328 | + |
329 | + |
330 | +def search_entries(self, cr, uid, data, context): |
331 | + |
332 | + search_due_date = data['form']['duedate'] |
333 | + pool = pooler.get_pool(cr.dbname) |
334 | + order_obj = pool.get('payment.order') |
335 | + line_obj = pool.get('account.move.line') |
336 | + invoice_obj = pool.get('account.invoice') |
337 | + payment = order_obj.browse(cr, uid, data['id'], |
338 | + context=context) |
339 | + ctx = '' |
340 | + if payment.mode: |
341 | + ctx = '''context="{'journal_id': %d}"''' % payment.mode.journal.id |
342 | + |
343 | + # Search for move line to pay: |
344 | + domain = [('reconcile_id', '=', False)]#,('account_id.type', '=', payment.type),('amount_to_pay', '<>', 0)] |
345 | +# domain = domain + ['|',('date_maturity','<',search_due_date),('date_maturity','=',False)] |
346 | + #if payment.mode: |
347 | + # domain = [('payment_type','=',payment.mode.type.id)] + domain |
348 | + sel_line_ids = line_obj.search(cr, uid, domain, order='date_maturity', context=context) |
349 | + line_ids = [] |
350 | + |
351 | + for line in pool.get('account.move.line').browse(cr, uid, sel_line_ids, context): |
352 | + if line.invoice.id: |
353 | + invoice_data = invoice_obj.browse(cr, uid, line.invoice.id, context=context) |
354 | + if invoice_data.next_payment_date: |
355 | + if invoice_data.next_payment_date >=search_due_date: |
356 | + line_ids.append(line.id) |
357 | + |
358 | + FORM.string = '''<?xml version="1.0" encoding="utf-8"?> |
359 | +<form string="Populate Payment:"> |
360 | + <field name="entries" colspan="4" height="300" width="800" nolabel="1" |
361 | + domain="[('id', 'in', [%s])]" %s/> |
362 | + <separator string="Extra message of payment communication" colspan="4"/> |
363 | + <field name="communication2" colspan="4"/> |
364 | +</form>''' % (','.join([str(x) for x in line_ids]), ctx) |
365 | + |
366 | + selected_ids = [] |
367 | + amount = data['form']['amount'] |
368 | + if amount: |
369 | + if payment.mode and payment.mode.require_bank_account: |
370 | + line2bank = pool.get('account.move.line').line2bank(cr, uid, line_ids, payment.mode.id, context) |
371 | + else: |
372 | + line2bank = None |
373 | + # If user specified an amount, search what moves match the criteria taking into account |
374 | + # if payment mode allows bank account to be null. |
375 | + for line in pool.get('account.move.line').browse(cr, uid, line_ids, context): |
376 | + if abs(line.amount_to_pay) <= amount: |
377 | + if line2bank and not line2bank.get(line.id): |
378 | + continue |
379 | + amount -= abs(line.amount_to_pay) |
380 | + selected_ids.append(line.id) |
381 | + return { |
382 | + 'entries': selected_ids, |
383 | + } |
384 | + |
385 | +def create_payment(self, cr, uid, data, context): |
386 | + line_ids= data['form']['entries'][0][2] |
387 | + if not line_ids: return {} |
388 | + |
389 | + pool= pooler.get_pool(cr.dbname) |
390 | + order_obj = pool.get('payment.order') |
391 | + line_obj = pool.get('account.move.line') |
392 | + invoice_obj = pool.get('account.invoice') |
393 | + payment_term_obj = pool.get('account.payment.term') |
394 | + payment = order_obj.browse(cr, uid, data['id'], |
395 | + context=context) |
396 | + t = payment.mode and payment.mode.type.id or None |
397 | + line2bank = pool.get('account.move.line').line2bank(cr, uid, |
398 | + line_ids, t, context) |
399 | + |
400 | + ## Finally populate the current payment with new lines: |
401 | + |
402 | + for line in line_obj.browse(cr, uid, line_ids, context=context): |
403 | + |
404 | + invoice_data = invoice_obj.browse(cr, uid, line.invoice.id, context=context) |
405 | + discount = 0.0 |
406 | + payment_data = payment_term_obj.browse(cr, uid, invoice_data.payment_term.id).cash_discount_ids |
407 | + if payment_data: |
408 | + cash_discount = payment_data[0].discount |
409 | + discount = invoice_data.amount_total * cash_discount /100 |
410 | + dis_next_date = invoice_data.next_payment_date |
411 | + |
412 | + if payment.date_prefered == "now": |
413 | + #no payment date => immediate payment |
414 | + date_to_pay = False |
415 | + elif payment.date_prefered == 'due': |
416 | + date_to_pay = line.date_maturity |
417 | + elif payment.date_prefered == 'fixed': |
418 | + date_to_pay = payment.date_planned |
419 | + |
420 | + pool.get('payment.line').create(cr,uid,{ |
421 | + 'move_line_id': line.id, |
422 | + 'amount_currency': line.amount_to_pay, |
423 | + 'bank_id': line2bank.get(line.id), |
424 | + 'order_id': payment.id, |
425 | + 'partner_id': line.partner_id and line.partner_id.id or False, |
426 | + 'communication': (line.ref and line.name!='/' and line.ref+'. '+line.name) or line.ref or line.name or '/', |
427 | + 'communication2': data['form']['communication2'], |
428 | + 'date': date_to_pay, |
429 | + 'currency': line.invoice and line.invoice.currency_id.id or False, |
430 | + 'account_id': line.account_id.id, |
431 | + 'cash_discount':discount, |
432 | + 'discount_date':dis_next_date, |
433 | + 'pay_amount':(line.amount_to_pay+discount), |
434 | + }, context=context) |
435 | + |
436 | + return {} |
437 | + |
438 | + |
439 | +class wizard_payment_order(wizard.interface): |
440 | + """ |
441 | + Create a payment object with lines corresponding to the account move line |
442 | + to pay according to the date provided by the user and the mode-type payment of the order. |
443 | + Hypothesis: |
444 | + - Small number of non-reconcilied move line , payment mode and bank account type, |
445 | + - Big number of partner and bank account. |
446 | + |
447 | + If a type is given, unsuitable account move lines are ignored. |
448 | + """ |
449 | + states = { |
450 | + |
451 | + 'init': { |
452 | + 'actions': [], |
453 | + 'result': { |
454 | + 'type': 'form', |
455 | + 'arch': arch_duedate, |
456 | + 'fields':field_duedate, |
457 | + 'state': [ |
458 | + ('end','_Cancel'), |
459 | + ('search','_Search', '', True) |
460 | + ] |
461 | + }, |
462 | + }, |
463 | + |
464 | + 'search': { |
465 | + 'actions': [search_entries], |
466 | + 'result': { |
467 | + 'type': 'form', |
468 | + 'arch': FORM, |
469 | + 'fields': FIELDS, |
470 | + 'state': [ |
471 | + ('end','_Cancel'), |
472 | + ('create','_Add to payment order', '', True) |
473 | + ] |
474 | + }, |
475 | + }, |
476 | + 'create': { |
477 | + 'actions': [], |
478 | + 'result': { |
479 | + 'type': 'action', |
480 | + 'action': create_payment, |
481 | + 'state': 'end'} |
482 | + }, |
483 | + } |
484 | + |
485 | +wizard_payment_order('populate_payment_ext111') |
486 | + |
487 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |