[MERGE] forward port of branch 8.0 up to 33a8989

This commit is contained in:
Christophe Simonis
2015-02-11 17:25:33 +01:00
106 changed files with 154060 additions and 276 deletions
+2 -1
View File
@@ -4,6 +4,7 @@ _build/
# dotfiles
.*
!.gitignore
!.mailmap
# compiled python files
*.py[co]
# setup.py egg_info
@@ -30,4 +31,4 @@ install/win32/meta.py
/lib/
/man/
/share/
/src/
/src/
+1164
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -1370,9 +1370,6 @@ class account_move(osv.osv):
self.validate(cr, uid, ids, context=context)
return result
#
# TODO: Check if period is closed !
#
def create(self, cr, uid, vals, context=None):
context = dict(context or {})
if vals.get('line_id'):
@@ -1534,6 +1531,8 @@ class account_move(osv.osv):
line_ids = []
line_draft_ids = []
company_id = None
# makes sure we don't use outdated period
obj_move_line._update_journal_check(cr, uid, journal.id, move.period_id.id, context=context)
for line in move.line_id:
amount += line.debit - line.credit
line_ids.append(line.id)
+1 -1
View File
@@ -1221,7 +1221,7 @@ class account_move_line(osv.osv):
period = period_obj.browse(cr, uid, period_id, context=context)
for (state,) in result:
if state == 'done':
raise UserError(_('You can not add/modify entries in a closed period %s of journal %s.' % (period.name,journal.name)))
raise UserError(_('You can not add/modify entries in a closed period %s of journal %s.') % (period.name,journal.name))
if not result:
jour_period_obj.create(cr, uid, {
'name': (journal.code or journal.name)+':'+(period.name or ''),
+7 -7
View File
@@ -127,14 +127,14 @@ class account_fiscal_position(osv.osv):
('auto_apply', '=', True),
'|', ('vat_required', '=', False), ('vat_required', '=', partner.vat_subjected),
]
if delivery.country_id.id:
fiscal_position_ids = self.search(cr, uid, domain + [('country_id', '=', delivery.country_id.id)], context=context, limit=1)
if fiscal_position_ids:
return fiscal_position_ids[0]
fiscal_position_ids = self.search(cr, uid, domain + [('country_id', '=', delivery.country_id.id)], context=context, limit=1)
if fiscal_position_ids:
return fiscal_position_ids[0]
fiscal_position_ids = self.search(cr, uid, domain + [('country_group_id.country_ids', '=', delivery.country_id.id)], context=context, limit=1)
if fiscal_position_ids:
return fiscal_position_ids[0]
fiscal_position_ids = self.search(cr, uid, domain + [('country_group_id.country_ids', '=', delivery.country_id.id)], context=context, limit=1)
if fiscal_position_ids:
return fiscal_position_ids[0]
fiscal_position_ids = self.search(cr, uid, domain + [('country_id', '=', None), ('country_group_id', '=', None)], context=context, limit=1)
if fiscal_position_ids:
+18 -28
View File
@@ -36,8 +36,15 @@ class account_invoice_report(osv.osv):
context={}
currency_obj = self.pool.get('res.currency')
currency_rate_obj = self.pool.get('res.currency.rate')
user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
currency_rate_id = currency_rate_obj.search(cr, uid, [('rate', '=', 1)], limit=1, context=context)[0]
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
user_currency_id = user.company_id.currency_id.id
currency_rate_id = currency_rate_obj.search(
cr, uid, [
('rate', '=', 1),
'|',
('currency_id.company_id', '=', user.company_id.id),
('currency_id.company_id', '=', False)
], limit=1, context=context)[0]
base_currency_id = currency_rate_obj.browse(cr, uid, currency_rate_id, context=context).currency_id.id
res = {}
ctx = context.copy()
@@ -133,15 +140,7 @@ class account_invoice_report(osv.osv):
SELECT min(ail.id) AS id,
ai.date_invoice AS date,
ail.product_id, ai.partner_id, ai.payment_term, ai.period_id, ail.account_analytic_id,
CASE
WHEN u.uom_type::text <> 'reference'::text
THEN ( SELECT product_uom.name
FROM product_uom
WHERE product_uom.uom_type::text = 'reference'::text
AND product_uom.active
AND product_uom.category_id = u.category_id LIMIT 1)
ELSE u.name
END AS uom_name,
u2.name AS uom_name,
ai.currency_id, ai.journal_id, ai.fiscal_position, ai.user_id, ai.company_id,
count(ail.*) AS nbr,
ai.type, ai.state, pt.categ_id, ai.date_due, ai.account_id, ail.account_id AS account_line_id,
@@ -161,11 +160,11 @@ class account_invoice_report(osv.osv):
THEN SUM(- ail.price_subtotal)
ELSE SUM(ail.price_subtotal)
END / CASE
WHEN SUM(ail.quantity / u.factor) <> 0::numeric
WHEN SUM(ail.quantity / u.factor * u2.factor) <> 0::numeric
THEN CASE
WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying::text, 'in_invoice'::character varying::text])
THEN SUM((- ail.quantity) / u.factor)
ELSE SUM(ail.quantity / u.factor)
THEN SUM((- ail.quantity) / u.factor * u2.factor)
ELSE SUM(ail.quantity / u.factor * u2.factor)
END
ELSE 1::numeric
END AS price_average,
@@ -173,17 +172,8 @@ class account_invoice_report(osv.osv):
WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying::text, 'in_invoice'::character varying::text])
THEN - ai.residual
ELSE ai.residual
END / CASE
WHEN (( SELECT count(l.id) AS count
FROM account_invoice_line l
LEFT JOIN account_invoice a ON a.id = l.invoice_id
WHERE a.id = ai.id)) <> 0
THEN ( SELECT count(l.id) AS count
FROM account_invoice_line l
LEFT JOIN account_invoice a ON a.id = l.invoice_id
WHERE a.id = ai.id)
ELSE 1::bigint
END::numeric AS residual,
END / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
count(*) AS residual,
ai.commercial_partner_id as commercial_partner_id,
partner.country_id
"""
@@ -197,17 +187,17 @@ class account_invoice_report(osv.osv):
LEFT JOIN product_product pr ON pr.id = ail.product_id
left JOIN product_template pt ON pt.id = pr.product_tmpl_id
LEFT JOIN product_uom u ON u.id = ail.uos_id
LEFT JOIN product_uom u2 ON u.id = pt.uom_id
LEFT JOIN product_uom u2 ON u2.id = pt.uom_id
"""
return from_str
def _group_by(self):
group_by_str = """
GROUP BY ail.product_id, ail.account_analytic_id, ai.date_invoice, ai.id,
ai.partner_id, ai.payment_term, ai.period_id, u.name, ai.currency_id, ai.journal_id,
ai.partner_id, ai.payment_term, ai.period_id, u2.name, u2.id, ai.currency_id, ai.journal_id,
ai.fiscal_position, ai.user_id, ai.company_id, ai.type, ai.state, pt.categ_id,
ai.date_due, ai.account_id, ail.account_id, ai.partner_bank_id, ai.residual,
ai.amount_total, u.uom_type, u.category_id, ai.commercial_partner_id, partner.country_id
ai.amount_total, ai.commercial_partner_id, partner.country_id
"""
return group_by_str
+1
View File
@@ -1,3 +1,4 @@
from . import test_tax
from . import test_search
from . import test_reconciliation
from . import test_account_move_closed_period
@@ -0,0 +1,41 @@
from datetime import date
from openerp.tests.common import TransactionCase
from openerp.osv.orm import except_orm
class TestPeriodState(TransactionCase):
"""
Forbid creation of Journal Entries for a closed period.
"""
def setUp(self):
super(TestPeriodState, self).setUp()
cr, uid = self.cr, self.uid
self.wizard_period_close = self.registry('account.period.close')
self.wizard_period_close_id = self.wizard_period_close.create(cr, uid, {'sure': 1})
_, self.sale_journal_id = self.registry("ir.model.data").get_object_reference(cr, uid, "account", "sales_journal")
_, self.period_9_id = self.registry("ir.model.data").get_object_reference(cr, uid, "account", "period_9")
def test_period_state(self):
cr, uid = self.cr, self.uid
self.wizard_period_close.data_save(cr, uid, [self.wizard_period_close_id], {
'lang': 'en_US',
'active_model': 'account.period',
'active_ids': [self.period_9_id],
'tz': False,
'active_id': self.period_9_id
})
with self.assertRaises(except_orm):
self.registry('account.move').create(cr, uid, {
'name': '/',
'period_id': self.period_9_id,
'journal_id': self.sale_journal_id,
'date': date.today(),
'line_id': [(0, 0, {
'name': 'foo',
'debit': 10,
}), (0, 0, {
'name': 'bar',
'credit': 10,
})]
})
+3 -1
View File
@@ -75,6 +75,8 @@ class account_invoice_line(osv.osv):
res: The move line entries produced so far by the parent move_line_get.
"""
inv = i_line.invoice_id
fiscal_pool = self.pool.get('account.fiscal.position')
fpos = inv.fiscal_position or False
company_currency = inv.company_id.currency_id.id
if i_line.product_id.type != 'service' and i_line.product_id.valuation == 'real_time':
@@ -110,7 +112,7 @@ class account_invoice_line(osv.osv):
'price_unit':price_unit,
'quantity':i_line.quantity,
'price': -1 * self._get_price(cr, uid, inv, company_currency, i_line, price_unit),
'account_id':cacc,
'account_id':fiscal_pool.map_account(cr, uid, fpos, cacc),
'product_id':i_line.product_id.id,
'uos_id':i_line.uos_id.id,
'account_analytic_id': False,
+2 -3
View File
@@ -145,7 +145,6 @@
<form string="Depreciation Lines">
<group>
<group>
<field name="asset_id" invisible="1"/>
<field name="parent_state" invisible="1"/>
<field name="name"/>
<field name="amount"/>
@@ -156,8 +155,8 @@
<group>
<field name="sequence"/>
<field name="depreciation_date"/>
<field name="depreciated_value" readonly="1"/>
<field name="remaining_value" readonly="1"/>
<field name="depreciated_value"/>
<field name="remaining_value"/>
</group>
</group>
</form>
@@ -100,9 +100,12 @@ class report_rappel(report_sxw.rml_parse):
partner_max_text = i.followup_line_id.description
text = partner_max_delay and partner_max_text or default_text
if text:
lang_obj = self.pool['res.lang']
lang_ids = lang_obj.search(self.cr, self.uid, [('code', '=', stat_line.partner_id.lang)], context=context)
date_format = lang_ids and lang_obj.browse(self.cr, self.uid, lang_ids[0], context=context).date_format or '%Y-%m-%d'
text = text % {
'partner_name': stat_line.partner_id.name,
'date': time.strftime('%Y-%m-%d'),
'date': time.strftime(date_format),
'company_name': stat_line.company_id.name,
'user_signature': self.pool['res.users'].browse(self.cr, self.uid, self.uid, context).signature or '',
}
@@ -4,6 +4,7 @@
<template id="report_followup">
<t t-call="report.html_container">
<t t-foreach="ids_to_objects(data['form']['partner_ids'])" t-as="o">
<t t-esc="setLang(o.partner_id.lang)"/>
<t t-call="report.external_layout">
<div class="page">
<div class="row">
+11 -3
View File
@@ -173,10 +173,11 @@ class calendar_attendee(osv.Model):
return res
def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False),
template_xmlid='calendar_template_meeting_invitation', context=None):
template_xmlid='calendar_template_meeting_invitation', force=False, context=None):
"""
Send mail for event invitation to event attendees.
@param email_from: email address for user sending the mail
@param force: If set to True, email will be sent to user himself. Usefull for example for alert, ...
"""
res = False
@@ -209,7 +210,7 @@ class calendar_attendee(osv.Model):
})
for attendee in self.browse(cr, uid, ids, context=context):
if attendee.email and email_from and attendee.email != email_from:
if attendee.email and email_from and (attendee.email != email_from or force):
ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context)
mail_id = template_pool.send_mail(cr, uid, template_id, attendee.id, context=local_context)
@@ -511,7 +512,14 @@ class calendar_alarm_manager(osv.AbstractModel):
alarm = self.pool['calendar.alarm'].browse(cr, uid, alert['alarm_id'], context=context)
if alarm.type == 'email':
res = self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, [att.id for att in event.attendee_ids], template_xmlid='calendar_template_meeting_reminder', context=context)
res = self.pool['calendar.attendee']._send_mail_to_attendees(
cr,
uid,
[att.id for att in event.attendee_ids],
template_xmlid='calendar_template_meeting_reminder',
force=True,
context=context
)
return res
+2 -2
View File
@@ -148,7 +148,7 @@
<field name="state_id" on_change="onchange_state(state_id)" options='{"no_open": True}' placeholder="State" style="width: 24%%"/>
<field name="zip" placeholder="ZIP" style="width: 34%%"/>
</div>
<field name="country_id" placeholder="Country" options='{"no_open": True}'/>
<field name="country_id" placeholder="Country" options='{"no_open": True, "no_create": True}'/>
</div>
</group>
<group>
@@ -492,7 +492,7 @@
<field name="state_id" options='{"no_open": True}' on_change="onchange_state(state_id)" placeholder="State" style="width: 24%%"/>
<field name="zip" placeholder="ZIP" style="width: 34%%"/>
</div>
<field name="country_id" placeholder="Country" options='{"no_open": True}'/>
<field name="country_id" placeholder="Country" options='{"no_open": True, "no_create": True}'/>
</div>
</group>
+26 -21
View File
@@ -40,6 +40,7 @@ from openerp.tools.translate import _
from openerp.exceptions import UserError
_logger = logging.getLogger(__name__)
MAX_POP_MESSAGES = 50
class fetchmail_server(osv.osv):
"""Incoming POP/IMAP mail server account"""
@@ -223,27 +224,31 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
imap_server.logout()
elif server.type == 'pop':
try:
pop_server = server.connect()
(numMsgs, totalSize) = pop_server.stat()
pop_server.list()
for num in range(1, numMsgs + 1):
(header, msges, octets) = pop_server.retr(num)
msg = '\n'.join(msges)
res_id = None
try:
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
msg,
save_original=server.original,
strip_attachments=(not server.attach),
context=context)
pop_server.dele(num)
except Exception:
_logger.info('Failed to process mail from %s server %s.', server.type, server.name, exc_info=True)
failed += 1
if res_id and server.action_id:
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids': [res_id], 'active_model': context.get("thread_model", server.object_id.model)})
cr.commit()
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", numMsgs, server.type, server.name, (numMsgs - failed), failed)
while True:
pop_server = server.connect()
(numMsgs, totalSize) = pop_server.stat()
pop_server.list()
for num in range(1, min(MAX_POP_MESSAGES, numMsgs) + 1):
(header, msges, octets) = pop_server.retr(num)
msg = '\n'.join(msges)
res_id = None
try:
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
msg,
save_original=server.original,
strip_attachments=(not server.attach),
context=context)
pop_server.dele(num)
except Exception:
_logger.info('Failed to process mail from %s server %s.', server.type, server.name, exc_info=True)
failed += 1
if res_id and server.action_id:
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids': [res_id], 'active_model': context.get("thread_model", server.object_id.model)})
cr.commit()
if numMsgs < MAX_POP_MESSAGES:
break
pop_server.quit()
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", numMsgs, server.type, server.name, (numMsgs - failed), failed)
except Exception:
_logger.info("General failure when trying to fetch mail from %s server %s.", server.type, server.name, exc_info=True)
finally:
+8 -4
View File
@@ -137,6 +137,7 @@ class google_service(osv.osv_memory):
status = 418
response = ""
ask_time = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
try:
if type.upper() == 'GET' or type.upper() == 'DELETE':
data = werkzeug.url_encode(params)
@@ -159,13 +160,16 @@ class google_service(osv.osv_memory):
try:
ask_time = datetime.strptime(request.headers.get('date'), "%a, %d %b %Y %H:%M:%S %Z")
except:
ask_time = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
pass
except urllib2.HTTPError, e:
if e.code in (400, 401, 410):
raise e
_logger.exception("Bad google request : %s !" % e.read())
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong with your request to google"), context=context)
elif e.code in (204, 404):
status = e.code
response = ""
else:
_logger.exception("Bad google request : %s !" % e.read())
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong with your request to google"), context=context)
return (status, response, ask_time)
def get_base_url(self, cr, uid, context=None):
+1 -1
View File
@@ -68,7 +68,7 @@
<page string="Personal Information" groups="base.group_hr_user">
<group>
<group string="Citizenship &amp; Other Information">
<field name="country_id" options='{"no_open": True}'/>
<field name="country_id" options='{"no_open": True, "no_create": True}'/>
<field name="identification_id" groups="base.group_hr_user"/>
<field name="passport_id" groups="base.group_hr_user"/>
<field name="bank_account_id"/>
+1 -4
View File
@@ -150,10 +150,7 @@ class hr_employee(osv.osv):
def _attendance_access(self, cr, uid, ids, name, args, context=None):
# this function field use to hide attendance button to singin/singout from menu
group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_hr_attendance')
visible = False
if uid in [user.id for user in group.users]:
visible = True
visible = self.pool.get("res.users").has_group(cr, uid, "base.group_hr_attendance")
return dict([(x, visible) for x in ids])
_columns = {
@@ -327,7 +327,8 @@ class hr_timesheet_line(osv.osv):
for ts_line in self.browse(cursor, user, ids, context=context):
sheet_ids = sheet_obj.search(cursor, user,
[('date_to', '>=', ts_line.date), ('date_from', '<=', ts_line.date),
('employee_id.user_id', '=', ts_line.user_id.id)],
('employee_id.user_id', '=', ts_line.user_id.id),
('state', 'in', ['draft', 'new'])],
context=context)
if sheet_ids:
# [0] because only one sheet possible for an employee between 2 dates
@@ -366,17 +367,11 @@ class hr_timesheet_line(osv.osv):
),
}
def _check_sheet_state(self, cr, uid, ids, context=None):
if context is None:
context = {}
for timesheet_line in self.browse(cr, uid, ids, context=context):
if timesheet_line.sheet_id and timesheet_line.sheet_id.state not in ('draft', 'new'):
return False
return True
_constraints = [
(_check_sheet_state, 'You cannot modify an entry in a Confirmed/Done timesheet !', ['state']),
]
def write(self, cr, uid, ids, values, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
self._check(cr, uid, ids)
return super(hr_timesheet_line, self).write(cr, uid, ids, values, context=context)
def unlink(self, cr, uid, ids, *args, **kwargs):
if isinstance(ids, (int, long)):
@@ -260,7 +260,9 @@ openerp.hr_timesheet_sheet = function(instance) {
return;
}
var ops = self.generate_o2m_value();
new instance.web.Model("hr.analytic.timesheet").call("on_change_account_id", [[], id]).then(function(res) {
new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], [id],
new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(res) {
res = res[id];
var def = _.extend({}, self.default_get, res.value, {
name: self.description_line,
unit_amount: 0,
+4
View File
@@ -0,0 +1,4 @@
The escpos directory contains the MIT licensed pyxmlescpos lib taken from
https://github.com/fvdsn/py-xml-escpos
+32 -19
View File
@@ -16,6 +16,14 @@ import pickle
import re
import subprocess
import traceback
try:
from .. escpos import *
from .. escpos.exceptions import *
from .. escpos.printer import Usb
except ImportError:
escpos = printer = None
from threading import Thread, Lock
from Queue import Queue, Empty
@@ -24,13 +32,6 @@ try:
except ImportError:
usb = None
try:
from .. import escpos
from ..escpos import printer
from ..escpos import supported_devices
except ImportError:
escpos = printer = None
from PIL import Image
from openerp import http
@@ -110,24 +111,19 @@ class EscposDriver(Thread):
self.start()
def get_escpos_printer(self):
try:
printers = self.connected_usb_devices()
if len(printers) > 0:
self.set_status('connected','Connected to '+printers[0]['name'])
return escpos.printer.Usb(printers[0]['vendor'], printers[0]['product'])
else:
self.set_status('disconnected','Printer Not Found')
return None
except Exception as e:
self.set_status('error',str(e))
printers = self.connected_usb_devices()
if len(printers) > 0:
self.set_status('connected','Connected to '+printers[0]['name'])
return Usb(printers[0]['vendor'], printers[0]['product'])
else:
self.set_status('disconnected','Printer Not Found')
return None
def get_status(self):
self.push_task('status')
return self.status
def open_cashbox(self,printer):
printer.cashdraw(2)
printer.cashdraw(5)
@@ -150,11 +146,13 @@ class EscposDriver(Thread):
_logger.warning('ESC/POS Device Disconnected: '+message)
def run(self):
if not escpos:
_logger.error('ESC/POS cannot initialize, please verify system dependencies.')
return
while True:
try:
error = True
timestamp, task, data = self.queue.get(True)
printer = self.get_escpos_printer()
@@ -162,6 +160,7 @@ class EscposDriver(Thread):
if printer == None:
if task != 'status':
self.queue.put((timestamp,task,data))
error = False
time.sleep(5)
continue
elif task == 'receipt':
@@ -178,11 +177,25 @@ class EscposDriver(Thread):
self.print_status(printer)
elif task == 'status':
pass
error = False
except NoDeviceError as e:
print "No device found %s" %str(e)
except HandleDeviceError as e:
print "Impossible to handle the device due to previous error %s" % str(e)
except TicketNotPrinted as e:
print "The ticket does not seems to have been fully printed %s" % str(e)
except NoStatusError as e:
print "Impossible to get the status of the printer %s" % str(e)
except Exception as e:
self.set_status('error', str(e))
errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n'
_logger.error(errmsg);
finally:
if error:
self.queue.put((timestamp, task, data))
if printer:
printer.close()
def push_task(self,task, data = None):
self.lockedstart()
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Frederic van der Essen & Manuel F. Martinez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+7
View File
@@ -8,6 +8,13 @@ CTL_FF = '\x0c' # Form feed
CTL_CR = '\x0d' # Carriage return
CTL_HT = '\x09' # Horizontal tab
CTL_VT = '\x0b' # Vertical tab
# RT Status commands
DLE_EOT_PRINTER = '\x10\x04\x01' # Transmit printer status
DLE_EOT_OFFLINE = '\x10\x04\x02'
DLE_EOT_ERROR = '\x10\x04\x03'
DLE_EOT_PAPER = '\x10\x04\x04'
# Printer hardware
HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes
HW_SELECT = '\x1b\x3d\x01' # Printer select
+4 -17
View File
@@ -1,13 +1,5 @@
# -*- coding: utf-8 -*-
'''
@author: Manuel F Martinez <manpaz@bashlinux.com>
@organization: Bashlinux
@copyright: Copyright (c) 2012 Bashlinux
@license: GPL
'''
import logging
import time
import copy
import io
@@ -21,19 +13,15 @@ import xml.dom.minidom as minidom
from PIL import Image
_logger = logging.getLogger(__name__)
try:
import jcconv
except ImportError:
jcconv = None
_logger.warning('ESC/POS: please install jcconv for improved Japanese receipt printing:\n # pip install jcconv')
try:
import qrcode
except ImportError:
qrcode = None
_logger.warning('ESC/POS: please install the qrcode python module for qrcode printing in point of sale receipts:\n # pip install qrcode')
from constants import *
from exceptions import *
@@ -107,14 +95,14 @@ class StyleStack:
'off': TXT_BOLD_OFF,
'on': TXT_BOLD_ON,
# must be issued after 'size' command
# because ESC ! resets ESC E
# because ESC ! resets ESC -
'_order': 10,
},
'font': {
'a': TXT_FONT_A,
'b': TXT_FONT_B,
# must be issued after 'size' command
# because ESC ! resets ESC M
# because ESC ! resets ESC -
'_order': 10,
},
'size': {
@@ -122,7 +110,7 @@ class StyleStack:
'double-height': TXT_2HEIGHT,
'double-width': TXT_2WIDTH,
'double': TXT_DOUBLE,
'_order': 1,
'_order': 1,
},
'color': {
'black': TXT_COLOR_BLACK,
@@ -181,9 +169,8 @@ class StyleStack:
def to_escpos(self):
""" converts the current style to an escpos command string """
cmd = ''
# Sort commands because some commands affect others (see _order attributes above)
ordered_cmds = self.cmds.keys()
ordered_cmds.sort(lambda x, y: cmp(self.cmds[x]['_order'], self.cmds[y]['_order']))
ordered_cmds.sort(lambda x,y: cmp(self.cmds[x]['_order'], self.cmds[y]['_order']))
for style in ordered_cmds:
cmd += self.cmds[style][self.get(style)]
return cmd
+36
View File
@@ -78,3 +78,39 @@ class CashDrawerError(Error):
def __str__(self):
return "Valid pin must be set to send pulse"
class NoStatusError(Error):
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 70
def __str__(self):
return "Impossible to get status from the printer"
class TicketNotPrinted(Error):
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 80
def __str__(self):
return "A part of the ticket was not been printed"
class NoDeviceError(Error):
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 90
def __str__(self):
return "Impossible to find the printer Device"
class HandleDeviceError(Error):
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 100
def __str__(self):
return "Impossible to handle device"
+85 -19
View File
@@ -1,10 +1,4 @@
#!/usr/bin/python
'''
@author: Manuel F Martinez <manpaz@bashlinux.com>
@organization: Bashlinux
@copyright: Copyright (c) 2012 Bashlinux
@license: GPL
'''
import usb.core
import usb.util
@@ -14,6 +8,7 @@ import socket
from escpos import *
from constants import *
from exceptions import *
from time import sleep
class Usb(Escpos):
""" Define USB printer """
@@ -26,6 +21,9 @@ class Usb(Escpos):
@param in_ep : Input end point
@param out_ep : Output end point
"""
self.errorText = "ERROR PRINTER\n\n\n\n\n\n"+PAPER_FULL_CUT
self.idVendor = idVendor
self.idProduct = idProduct
self.interface = interface
@@ -33,35 +31,102 @@ class Usb(Escpos):
self.out_ep = out_ep
self.open()
def open(self):
""" Search device on USB tree and set is as escpos device """
self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
if self.device is None:
print "Cable isn't plugged in"
if self.device.is_kernel_driver_active(0):
try:
self.device.detach_kernel_driver(0)
except usb.core.USBError as e:
print "Could not detatch kernel driver: %s" % str(e)
raise NoDeviceError()
try:
if self.device.is_kernel_driver_active(self.interface):
self.device.detach_kernel_driver(self.interface)
self.device.set_configuration()
self.device.reset()
usb.util.claim_interface(self.device, self.interface)
except usb.core.USBError as e:
print "Could not set configuration: %s" % str(e)
raise HandleDeviceError(e)
def close(self):
i = 0
while True:
try:
if not self.device.is_kernel_driver_active(self.interface):
usb.util.release_interface(self.device, self.interface)
self.device.attach_kernel_driver(self.interface)
usb.util.dispose_resources(self.device)
else:
self.device = None
return True
except usb.core.USBError as e:
i += 1
if i > 100:
return False
sleep(0.1)
def _raw(self, msg):
""" Print any command sent in raw format """
self.device.write(self.out_ep, msg, self.interface)
if len(msg) != self.device.write(self.out_ep, msg, self.interface):
self.device.write(self.out_ep, self.errorText, self.interface)
raise TicketNotPrinted()
def __extract_status(self):
maxiterate = 0
rep = None
while rep == None:
maxiterate += 1
if maxiterate > 10000:
raise NoStatusError()
r = self.device.read(self.in_ep, 20, self.interface).tolist()
while len(r):
rep = r.pop()
return rep
def get_printer_status(self):
status = {
'printer': {},
'offline': {},
'error' : {},
'paper' : {},
}
self.device.write(self.out_ep, DLE_EOT_PRINTER, self.interface)
printer = self.__extract_status()
self.device.write(self.out_ep, DLE_EOT_OFFLINE, self.interface)
offline = self.__extract_status()
self.device.write(self.out_ep, DLE_EOT_ERROR, self.interface)
error = self.__extract_status()
self.device.write(self.out_ep, DLE_EOT_PAPER, self.interface)
paper = self.__extract_status()
status['printer']['status_code'] = printer
status['printer']['status_error'] = not ((printer & 147) == 18)
status['printer']['online'] = not bool(printer & 8)
status['printer']['recovery'] = bool(printer & 32)
status['printer']['paper_feed_on'] = bool(printer & 64)
status['printer']['drawer_pin_high'] = bool(printer & 4)
status['offline']['status_code'] = offline
status['offline']['status_error'] = not ((offline & 147) == 18)
status['offline']['cover_open'] = bool(offline & 4)
status['offline']['paper_feed_on'] = bool(offline & 8)
status['offline']['paper'] = not bool(offline & 32)
status['offline']['error'] = bool(offline & 64)
status['error']['status_code'] = error
status['error']['status_error'] = not ((error & 147) == 18)
status['error']['recoverable'] = bool(error & 4)
status['error']['autocutter'] = bool(error & 8)
status['error']['unrecoverable'] = bool(error & 32)
status['error']['auto_recoverable'] = not bool(error & 64)
status['paper']['status_code'] = paper
status['paper']['status_error'] = not ((paper & 147) == 18)
status['paper']['near_end'] = bool(paper & 12)
status['paper']['present'] = not bool(paper & 96)
return status
def __del__(self):
""" Release USB interface """
if self.device:
usb.util.dispose_resources(self.device)
self.close()
self.device = None
@@ -134,3 +199,4 @@ class Network(Escpos):
def __del__(self):
""" Close TCP connection """
self.device.close()
+32 -33
View File
@@ -168,41 +168,41 @@ class Scanner(Thread):
device.ungrab()
except Exception as e:
self.set_status('error',str(e))
device = self.get_device()
if not device:
time.sleep(5) # wait until a suitable device is plugged
else:
try:
device.grab()
shift = False
barcode = []
time.sleep(5) # wait until a suitable device is plugged
device = self.get_device()
while True: # keycode loop
r,w,x = select([device],[],[],5)
if len(r) == 0: # timeout
break
events = device.read()
try:
device.grab()
shift = False
barcode = []
for event in events:
if event.type == evdev.ecodes.EV_KEY:
#_logger.debug('Evdev Keyboard event %s',evdev.categorize(event))
if event.value == 1: # keydown events
if event.code in self.keymap:
if shift:
barcode.append(self.keymap[event.code][1])
else:
barcode.append(self.keymap[event.code][0])
elif event.code == 42 or event.code == 54: # SHIFT
shift = True
elif event.code == 28: # ENTER, end of barcode
self.barcodes.put( (time.time(),''.join(barcode)) )
barcode = []
elif event.value == 0: #keyup events
if event.code == 42 or event.code == 54: # LEFT SHIFT
shift = False
while True: # keycode loop
r,w,x = select([device],[],[],5)
if len(r) == 0: # timeout
break
events = device.read()
except Exception as e:
self.set_status('error',str(e))
for event in events:
if event.type == evdev.ecodes.EV_KEY:
#_logger.debug('Evdev Keyboard event %s',evdev.categorize(event))
if event.value == 1: # keydown events
if event.code in self.keymap:
if shift:
barcode.append(self.keymap[event.code][1])
else:
barcode.append(self.keymap[event.code][0])
elif event.code == 42 or event.code == 54: # SHIFT
shift = True
elif event.code == 28: # ENTER, end of barcode
self.barcodes.put( (time.time(),''.join(barcode)) )
barcode = []
elif event.value == 0: #keyup events
if event.code == 42 or event.code == 54: # LEFT SHIFT
shift = False
except Exception as e:
self.set_status('error',str(e))
s = Scanner()
@@ -212,5 +212,4 @@ class ScannerDriver(hw_proxy.Proxy):
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
def scanner(self):
return s.get_barcode()
+24
View File
@@ -0,0 +1,24 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2014-2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import l10n_be_intrastat
import wizard
+43
View File
@@ -0,0 +1,43 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2014-2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Belgian Intrastat Declaration',
'version': '1.0',
'category': 'Reporting',
'description': """
Generates Intrastat XML report for declaration
Based on invoices.
""",
'author': 'Odoo SA',
'depends': ['report_intrastat', 'sale_stock', 'account_accountant', 'l10n_be'],
'data': [
'data/regions.xml',
'data/report.intrastat.code.csv',
'data/transaction.codes.xml',
'data/transport.modes.xml',
'security/groups.xml',
'security/ir.model.access.csv',
'l10n_be_intrastat.xml',
'wizard/l10n_be_intrastat_xml_view.xml',
],
'installable': True,
}
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="l10n_be_intrastat.intrastat_region_1" model="l10n_be_intrastat.region">
<field name="code">1</field>
<field name="name">Flemish region</field>
<field name="country_id" ref="base.be"/>
<field name="description">Includes Antwerp, Flemish Brabant, West Flanders, East Flanders and Limburg provinces</field>
</record>
<record id="l10n_be_intrastat.intrastat_region_2" model="l10n_be_intrastat.region">
<field name="code">2</field>
<field name="name">Walloon region</field>
<field name="country_id" ref="base.be"/>
<field name="description">Includes Walloon Brabant, Hainaut, Liège, Luxembourg and Namur provinces</field>
</record>
<record id="l10n_be_intrastat.intrastat_region_3" model="l10n_be_intrastat.region">
<field name="code">3</field>
<field name="name">Brussels region</field>
<field name="country_id" ref="base.be"/>
<field name="description">Includes 19 districts</field>
</record>
</data>
</openerp>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="l10n_be_intrastat.intrastat_transaction_1" model="l10n_be_intrastat.transaction">
<field name="code">1</field>
<field name="description">Transactions involving an actual or intended transfer of ownership from residents to non-residents against financial or other compensation (except the transactions listed under 2, 7 and 8)</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_2" model="l10n_be_intrastat.transaction">
<field name="code">2</field>
<field name="description">Return and replacement of goods free of charge after registration of the original transaction</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_3" model="l10n_be_intrastat.transaction">
<field name="code">3</field>
<field name="description">Transactions involving transfer of ownership without financial or in kind compensation (e.g. aid shipments)</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_4" model="l10n_be_intrastat.transaction">
<field name="code">4</field>
<field name="description">Operations with a view to processing 1 under contract (no transfer of ownership to the processor)</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_5" model="l10n_be_intrastat.transaction">
<field name="code">5</field>
<field name="description">Operations following processing under contract (no transfer of ownership to the processor)</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_6" model="l10n_be_intrastat.transaction">
<field name="code">6</field>
<field name="description">Operations "following" repair or maintenance, other than under joint inter-governmental production programmes. Repair and maintenance can be against payment or free of charge. These operations do not involve a (future) transfer of ownership*.</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_7" model="l10n_be_intrastat.transaction">
<field name="code">7</field>
<field name="description">Operations under joint defence projects or other joint intergovernmental production programmes</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_8" model="l10n_be_intrastat.transaction">
<field name="code">8</field>
<field name="description">Transactions involving the supply of building materials and technical equipment under a general construction or civil engineering contract for which no separate invoicing of the goods is required and an invoice for the total contract is issued</field>
</record>
<record id="l10n_be_intrastat.intrastat_transaction_9" model="l10n_be_intrastat.transaction">
<field name="code">9</field>
<field name="description">Other transactions which cannot be classified under other codes</field>
</record>
</data>
</openerp>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="l10n_be_intrastat.intrastat_trmode_1" model="l10n_be_intrastat.transport_mode">
<field name="code">1</field>
<field name="name">Sea transport (including wagons, motor vehicles, trailers, semi-trailers and lighters on board of a ship)</field>
</record>
<record id="l10n_be_intrastat.intrastat_trmode_2" model="l10n_be_intrastat.transport_mode">
<field name="code">2</field>
<field name="name">Railway transport (including lorries on railway wagons)</field>
</record>
<record id="l10n_be_intrastat.intrastat_trmode_3" model="l10n_be_intrastat.transport_mode">
<field name="code">3</field>
<field name="name">Road transport</field>
</record>
<record id="l10n_be_intrastat.intrastat_trmode_4" model="l10n_be_intrastat.transport_mode">
<field name="code">4</field>
<field name="name">Air transport</field>
</record>
<record id="l10n_be_intrastat.intrastat_trmode_5" model="l10n_be_intrastat.transport_mode">
<field name="code">5</field>
<field name="name">Postal consignments</field>
</record>
<record id="l10n_be_intrastat.intrastat_trmode_7" model="l10n_be_intrastat.transport_mode">
<field name="code">7</field>
<field name="name">Fixed transport installations (e.g. pipelines, high-tension cables)</field>
</record>
<record id="l10n_be_intrastat.intrastat_trmode_8" model="l10n_be_intrastat.transport_mode">
<field name="code">8</field>
<field name="name">Inland waterway transport</field>
</record>
<record id="l10n_be_intrastat.intrastat_trmode_9" model="l10n_be_intrastat.transport_mode">
<field name="code">9</field>
<field name="name">Own propulsion (imported or exported means of transport crossing the border under their own power, e.g. aircraft, lorries, boats, etc.)</field>
</record>
</data>
</openerp>
@@ -0,0 +1,67 @@
Intrastat Module
++++++++++++++++
This module can generate legal electronic reports for intrastat declaration
1 Security groups
*****************
There's two security groups, a standard and an extended, that can be used to view/hide fields linked to the intrastat declaration. The extended group is mainly here to hide the two useless fields if you don't have to send an extended declaration, "Means of transport" and "Incoterm".
2 Intrastat codes
*****************
The system must be able to associate an intrastat code to each product exported. The system can do this in different ways.
Each product can have a category, and each category can have a parent category. To retrieve the intrastat code associated to a product, the system search in this order :
#. check if product have an intrastat code
#. if not, check if product's category have an intrastat code
#. if not, check parent category if it have an intrastat code
#. if not, check parent category of parent category if it have an intrastat code (and so on)
#. if none found, an error is shown
So, in a user point of view, to easily record your intrastat codes, you can procede on the low-level categories, and then, for the product in a category with a wrong code for this item, set the intrastat code in the product form
3 Country of destination
************************
Intrastat declares all movements from an EU member country to another. In an invoice, a zone called "intrastat country" is used to determine the destination of goods. If this field is empty, the invoiced partner's country is used.
==============
Direct invoice
==============
If you create an invoice directly, the intrastat country is empty by default, and the invoiced partner's country is used. If the country of destination is different from the country of invoice, simply fill in the field "Intrastat country".
=======================
Invoice from sale order
=======================
If you create an invoice from a sale order, there are two cases. If you're not using different addresses for invoicing and delivery, the country of the partner is used to fill in the "Intrastat country" of the invoice
If you're using different addresses, the country of delivery address is used, if there's none, the country of the partner's address, and if none, the country of invoice address.
=====================
Invoice from purchase
=====================
If you create an invoice from a purchase order, the "Intrastat country" field is filled with the partner address.
4 Regions, Type of transaction
******************************
In the intrastat declaration the system must fill an information regarding the region of origin of products, and an information regarding the type of transaction.
======
Region
======
The region of origin of products is determined by using two parameters. If your invoice is linked to a sale order or to a purchase order, the system go to the order, check if the order is linked to a warehouse, and check the warehouse region.
If the system hasn't find a region, the system goes to the company, and check the region of the company. If there's none, an error message is generated.
===================
Type of transaction
===================
Intrastat define a list of transactions types. To determine the type of transaction for a particular invoice, the system check if a transaction type is selected in the invoice. If not, the type 1 is selected by default.
@@ -0,0 +1,454 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_be_intrastat
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-03 18:32+0000\n"
"PO-Revision-Date: 2015-02-03 18:32+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "April"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,arrivals:0
msgid "Arrivals"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "August"
msgstr ""
#. module: l10n_be_intrastat
#: model:l10n_be_intrastat.region,name:l10n_be_intrastat.intrastat_region_3
msgid "Brussels region"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat_xml.xml_decl:l10n_be_intrastat.view_intrastat_declaration_xml
msgid "Cancel"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat_xml.xml_decl:l10n_be_intrastat.view_intrastat_declaration_xml
msgid "Close"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,code:0
#: field:l10n_be_intrastat.transaction,code:0
#: field:l10n_be_intrastat.transport_mode,code:0
msgid "Code"
msgstr ""
#. module: l10n_be_intrastat
#: sql_constraint:l10n_be_intrastat.region:0
#: sql_constraint:l10n_be_intrastat.transaction:0
#: sql_constraint:l10n_be_intrastat.transport_mode:0
msgid "Code must be unique."
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_res_company
msgid "Companies"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,tax_code_id:0
msgid "Company Tax Chart"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,country_id:0
msgid "Country"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat_xml.xml_decl:l10n_be_intrastat.view_intrastat_declaration_xml
msgid "Create XML"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,create_uid:0
#: field:l10n_be_intrastat.transaction,create_uid:0
#: field:l10n_be_intrastat.transport_mode,create_uid:0
#: field:l10n_be_intrastat_xml.xml_decl,create_uid:0
msgid "Created by"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,create_date:0
#: field:l10n_be_intrastat.transaction,create_date:0
#: field:l10n_be_intrastat.transport_mode,create_date:0
#: field:l10n_be_intrastat_xml.xml_decl,create_date:0
msgid "Created on"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "December"
msgstr ""
#. module: l10n_be_intrastat
#: field:res.company,incoterm_id:0
msgid "Default incoterm for Intrastat"
msgstr ""
#. module: l10n_be_intrastat
#: field:res.company,transport_mode_id:0
msgid "Default transport mode"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,description:0
#: field:l10n_be_intrastat.transaction,description:0
#: field:l10n_be_intrastat.transport_mode,name:0
msgid "Description"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,dispatches:0
msgid "Dispatches"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,state:0
msgid "Download"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,state:0
msgid "Draft"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,arrivals:0
#: selection:l10n_be_intrastat_xml.xml_decl,dispatches:0
msgid "Exempt"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,arrivals:0
#: selection:l10n_be_intrastat_xml.xml_decl,dispatches:0
msgid "Extended"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "February"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,name:0
msgid "File Name"
msgstr ""
#. module: l10n_be_intrastat
#: model:l10n_be_intrastat.region,name:l10n_be_intrastat.intrastat_region_1
msgid "Flemish region"
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:96
#, python-format
msgid "Go to company configuration screen"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat_xml.xml_decl:l10n_be_intrastat.view_intrastat_declaration_xml
msgid "Here is the XML file with your intrastat declaration:"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,id:0
#: field:l10n_be_intrastat.transaction,id:0
#: field:l10n_be_intrastat.transport_mode,id:0
#: field:l10n_be_intrastat_xml.xml_decl,id:0
msgid "ID"
msgstr ""
#. module: l10n_be_intrastat
#: field:account.invoice,incoterm_id:0
msgid "Incoterm"
msgstr ""
#. module: l10n_be_intrastat
#: help:account.invoice,incoterm_id:0
#: help:res.company,incoterm_id:0
msgid "International Commercial Terms are a series of predefined commercial terms used in international transactions."
msgstr ""
#. module: l10n_be_intrastat
#: view:product.category:l10n_be_intrastat.l10n_be_intrastat_xml_decl_product_category_view
msgid "Intrastat"
msgstr ""
#. module: l10n_be_intrastat
#: field:product.category,intrastat_id:0
msgid "Intrastat Code"
msgstr ""
#. module: l10n_be_intrastat
#: field:account.invoice,intrastat_country_id:0
msgid "Intrastat Country"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.actions.act_window,name:l10n_be_intrastat.action_intrastat_xml_declaration
#: model:ir.ui.menu,name:l10n_be_intrastat.l10n_be_intrastat_xml_declaration
#: view:l10n_be_intrastat_xml.xml_decl:l10n_be_intrastat.view_intrastat_declaration_xml
msgid "Intrastat Declaration"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,file_save:0
msgid "Intrastat Report File"
msgstr ""
#. module: l10n_be_intrastat
#: field:account.invoice,intrastat_transaction_id:0
msgid "Intrastat Transaction Type"
msgstr ""
#. module: l10n_be_intrastat
#: field:account.invoice,transport_mode_id:0
msgid "Intrastat Transport Mode"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_l10n_be_intrastat_xml_xml_decl
msgid "Intrastat XML Declaration"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_report_intrastat_code
msgid "Intrastat code"
msgstr ""
#. module: l10n_be_intrastat
#: help:account.invoice,intrastat_country_id:0
msgid "Intrastat country, delivery for sales, origin for purchases"
msgstr ""
#. module: l10n_be_intrastat
#: model:res.groups,name:l10n_be_intrastat.intrastat_extended
msgid "Intrastat extended"
msgstr ""
#. module: l10n_be_intrastat
#: help:account.invoice,intrastat_transaction_id:0
msgid "Intrastat nature of transaction"
msgstr ""
#. module: l10n_be_intrastat
#: field:res.company,region_id:0
#: field:stock.warehouse,region_id:0
msgid "Intrastat region"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat.transaction:l10n_be_intrastat.intra_view_transaction_form
msgid "Intrastat transaction form"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat.transaction:l10n_be_intrastat.intra_view_transaction_tree
msgid "Intrastat transaction types"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat.transport_mode:l10n_be_intrastat.intra_view_transport_mode_form
msgid "Intrastat transportation mode form"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat.transport_mode:l10n_be_intrastat.intra_view_transport_mode_tree
msgid "Intrastat transportation modes"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_account_invoice
msgid "Invoice"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "January"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "July"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "June"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,write_uid:0
#: field:l10n_be_intrastat.transaction,write_uid:0
#: field:l10n_be_intrastat.transport_mode,write_uid:0
#: field:l10n_be_intrastat_xml.xml_decl,write_uid:0
msgid "Last Updated by"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,write_date:0
#: field:l10n_be_intrastat.transaction,write_date:0
#: field:l10n_be_intrastat.transport_mode,write_date:0
#: field:l10n_be_intrastat_xml.xml_decl,write_date:0
msgid "Last Updated on"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "March"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "May"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,month:0
msgid "Month"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat.region,name:0
msgid "Name"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "November"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "October"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_product_product
msgid "Product"
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:291
#, python-format
msgid "Product \"%s\" has no intrastat code, please configure it"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_product_category
msgid "Product Category"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_purchase_order
msgid "Purchase Order"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:156
#, python-format
msgid "Save"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,month:0
msgid "September"
msgstr ""
#. module: l10n_be_intrastat
#: selection:l10n_be_intrastat_xml.xml_decl,arrivals:0
#: selection:l10n_be_intrastat_xml.xml_decl,dispatches:0
msgid "Standard"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,state:0
msgid "State"
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:280
#, python-format
msgid "The Intrastat Region of the selected company is not set, please make sure to configure it first."
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:109
#, python-format
msgid "The country of your company is not set, please make sure to configure it first."
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:316
#, python-format
msgid "The default Incoterm of your company is not set, please make sure to configure it first."
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:304
#, python-format
msgid "The default Intrastat transport mode of your company is not set, please make sure to configure it first."
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:116
#, python-format
msgid "The registry number of your company is not set, please make sure to configure it first."
msgstr ""
#. module: l10n_be_intrastat
#: model:l10n_be_intrastat.region,name:l10n_be_intrastat.intrastat_region_2
msgid "Walloon region"
msgstr ""
#. module: l10n_be_intrastat
#: model:ir.model,name:l10n_be_intrastat.model_stock_warehouse
msgid "Warehouse"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat_xml.xml_decl:l10n_be_intrastat.view_intrastat_declaration_xml
msgid "XML Intratstat Declaration"
msgstr ""
#. module: l10n_be_intrastat
#: field:l10n_be_intrastat_xml.xml_decl,year:0
msgid "Year"
msgstr ""
#. module: l10n_be_intrastat
#: code:addons/l10n_be_intrastat/wizard/xml_decl.py:120
#, python-format
msgid "Year must be 4 digits number (YYYY)"
msgstr ""
#. module: l10n_be_intrastat
#: view:l10n_be_intrastat_xml.xml_decl:l10n_be_intrastat.view_intrastat_declaration_xml
msgid "or"
msgstr ""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,203 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2014-2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, osv
class account_invoice(osv.osv):
_inherit = "account.invoice"
_columns = {
'incoterm_id': fields.many2one(
'stock.incoterms', 'Incoterm',
help="International Commercial Terms are a series of predefined commercial terms "
"used in international transactions."),
'intrastat_transaction_id': fields.many2one(
'l10n_be_intrastat.transaction', 'Intrastat Transaction Type',
help="Intrastat nature of transaction"),
'transport_mode_id': fields.many2one(
'l10n_be_intrastat.transport_mode', 'Intrastat Transport Mode'),
'intrastat_country_id': fields.many2one(
'res.country', 'Intrastat Country',
help='Intrastat country, delivery for sales, origin for purchases',
domain=[('intrastat','=',True)]),
}
class intrastat_region(osv.osv):
_name = 'l10n_be_intrastat.region'
_columns = {
'code': fields.char('Code', required=True),
'country_id': fields.many2one('res.country', 'Country'),
'name': fields.char('Name', translate=True),
'description': fields.char('Description'),
}
_sql_constraints = [
('l10n_be_intrastat_regioncodeunique', 'UNIQUE (code)', 'Code must be unique.'),
]
class intrastat_transaction(osv.osv):
_name = 'l10n_be_intrastat.transaction'
_rec_name = 'code'
_columns = {
'code': fields.char('Code', required=True, readonly=True),
'description': fields.text('Description', readonly=True),
}
_sql_constraints = [
('l10n_be_intrastat_trcodeunique', 'UNIQUE (code)', 'Code must be unique.'),
]
class intrastat_transport_mode(osv.osv):
_name = 'l10n_be_intrastat.transport_mode'
_columns = {
'code': fields.char('Code', required=True, readonly=True),
'name': fields.char('Description', readonly=True),
}
_sql_constraints = [
('l10n_be_intrastat_trmodecodeunique', 'UNIQUE (code)', 'Code must be unique.'),
]
class product_category(osv.osv):
_name = "product.category"
_inherit = "product.category"
_columns = {
'intrastat_id': fields.many2one('report.intrastat.code', 'Intrastat Code'),
}
def get_intrastat_recursively(self, cr, uid, category, context=None):
""" Recursively search in categories to find an intrastat code id
:param category : Browse record of a category
"""
if category.intrastat_id:
res = category.intrastat_id.id
elif category.parent_id:
res = self.get_intrastat_recursively(cr, uid, category.parent_id, context=context)
else:
res = None
return res
class product_product(osv.osv):
_name = "product.product"
_inherit = "product.product"
def get_intrastat_recursively(self, cr, uid, id, context=None):
""" Recursively search in categories to find an intrastat code id
"""
product = self.browse(cr, uid, id, context=context)
if product.intrastat_id:
res = product.intrastat_id.id
elif product.categ_id:
res = self.pool['product.category'].get_intrastat_recursively(
cr, uid, product.categ_id, context=context)
else:
res = None
return res
class purchase_order(osv.osv):
_inherit = "purchase.order"
def _prepare_invoice(self, cr, uid, order, line_ids, context=None):
"""
copy incoterm from purchase order to invoice
"""
invoice = super(purchase_order, self)._prepare_invoice(
cr, uid, order, line_ids, context=context)
if order.incoterm_id:
invoice['incoterm_id'] = order.incoterm_id.id
#Try to determine products origin
if order.partner_id.country_id:
#It comes from supplier
invoice['intrastat_country_id'] = order.partner_id.country_id.id
return invoice
class report_intrastat_code(osv.osv):
_inherit = "report.intrastat.code"
_columns = {
'description': fields.text('Description', translate=True),
}
class res_company(osv.osv):
_inherit = "res.company"
_columns = {
'region_id': fields.many2one('l10n_be_intrastat.region', 'Intrastat region'),
'transport_mode_id': fields.many2one('l10n_be_intrastat.transport_mode',
'Default transport mode'),
'incoterm_id': fields.many2one('stock.incoterms', 'Default incoterm for Intrastat',
help="International Commercial Terms are a series of "
"predefined commercial terms used in international "
"transactions."),
}
class sale_order(osv.osv):
_inherit = "sale.order"
def _prepare_invoice(self, cr, uid, saleorder, lines, context=None):
"""
copy incoterm from sale order to invoice
"""
invoice = super(sale_order, self)._prepare_invoice(
cr, uid, saleorder, lines, context=context)
if saleorder.incoterm:
invoice['incoterm_id'] = saleorder.incoterm.id
# Guess products destination
if saleorder.partner_shipping_id.country_id:
invoice['intrastat_country_id'] = saleorder.partner_shipping_id.country_id.id
elif saleorder.partner_id.country_id:
invoice['intrastat_country_id'] = saleorder.partner_id.country_id.id
elif saleorder.partner_invoice_id.country_id:
invoice['intrastat_country_id'] = saleorder.partner_invoice_id.country_id.id
return invoice
class stock_warehouse(osv.osv):
_inherit = "stock.warehouse"
_columns = {
'region_id': fields.many2one('l10n_be_intrastat.region', 'Intrastat region'),
}
def get_regionid_from_locationid(self, cr, uid, location_id, context=None):
location_model = self.pool['stock.location']
location = location_model.browse(cr, uid, location_id, context=context)
location_ids = location_model.search(cr, uid,
[('parent_left', '<=', location.parent_left),
('parent_right', '>=', location.parent_right)],
context=context)
warehouse_ids = self.search(cr, uid,
[('lot_stock_id', 'in', location_ids),
('region_id', '!=', False)],
context=context)
warehouses = self.browse(cr, uid, warehouse_ids, context=context)
if warehouses and warehouses[0]:
return warehouses[0].region_id.id
return None
@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Customer Invoice Form -->
<record id="l10n_be_intrastat_xml_decl_cust_invoice_view" model="ir.ui.view">
<field name="name">l10n_be_intrastat_xml.custinvoice_form</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='date_due']" position="after">
<field name="incoterm_id" groups="l10n_be_intrastat.intrastat_extended"/>
</xpath>
<xpath expr="//field[@name='move_id']" position="after">
<field name="intrastat_transaction_id"/>
<field name="transport_mode_id" groups="l10n_be_intrastat.intrastat_extended"/>
<field name="intrastat_country_id"/>
</xpath>
</field>
</record>
<!-- Transaction Form -->
<record model="ir.ui.view" id="intra_view_transaction_form">
<field name="name">intra.view.transaction_form</field>
<field name="model">l10n_be_intrastat.transaction</field>
<field name="arch" type="xml">
<form string="Intrastat transaction form">
<group>
<field name="code"/>
<field name="description"/>
</group>
</form>
</field>
</record>
<!-- Transaction List -->
<record model="ir.ui.view" id="intra_view_transaction_tree">
<field name="name">intra.view.transaction_tree</field>
<field name="model">l10n_be_intrastat.transaction</field>
<field name="arch" type="xml">
<tree string="Intrastat transaction types">
<field name="code"/>
<field name="description"/>
</tree>
</field>
</record>
<!-- Transport mode Form -->
<record model="ir.ui.view" id="intra_view_transport_mode_form">
<field name="name">intra.view.transport_mode_form</field>
<field name="model">l10n_be_intrastat.transport_mode</field>
<field name="arch" type="xml">
<form string="Intrastat transportation mode form">
<group>
<field name="code"/>
<field name="name"/>
</group>
</form>
</field>
</record>
<!-- Transport mode List -->
<record model="ir.ui.view" id="intra_view_transport_mode_tree">
<field name="name">intra.view.transport_mode_tree</field>
<field name="model">l10n_be_intrastat.transport_mode</field>
<field name="arch" type="xml">
<tree string="Intrastat transportation modes">
<field name="code"/>
<field name="name"/>
</tree>
</field>
</record>
<!-- Category Form -->
<record id="l10n_be_intrastat_xml_decl_product_category_view" model="ir.ui.view">
<field name="name">l10n_be_intrastat_xml.productcategory_form</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="product.product_category_form_view"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='parent']" position="inside">
<group string="Intrastat" colspan="2">
<field name="intrastat_id"/>
</group>
</xpath>
</field>
</record>
<!-- Company -->
<record id="l10n_be_intrastat_xml_decl_res_company_form_view" model="ir.ui.view">
<field name="name">l10n_be_intrastat_xml.rescompany_form</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='company_registry']" position="after">
<field name="region_id" domain="[('country_id','=',country_id)]"/>
<field name="transport_mode_id" groups="l10n_be_intrastat.intrastat_extended"/>
<field name="incoterm_id" groups="l10n_be_intrastat.intrastat_extended"/>
</xpath>
</field>
</record>
<!-- Warehouse -->
<record id="l10n_be_intrastat_xml_decl_stock_warehouse_form_view" model="ir.ui.view">
<field name="name">l10n_be_intrastat_xml.stockwarehouse_form</field>
<field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="region_id"/>
</xpath>
</field>
</record>
<!-- Supplier Invoice Form -->
<record id="l10n_be_intrastat_xml_decl_supp_invoice_view" model="ir.ui.view">
<field name="name">l10n_be_intrastat_xml.suppinvoice_form</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='payment_term']" position="after">
<field name="incoterm_id" groups="l10n_be_intrastat.intrastat_extended"/>
</xpath>
<xpath expr="//field[@name='period_id']" position="after">
<field name="intrastat_transaction_id" />
<field name="transport_mode_id" groups="l10n_be_intrastat.intrastat_extended"/>
<field name="intrastat_country_id" />
</xpath>
</field>
</record>
</data>
</openerp>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="intrastat_extended" model="res.groups">
<field name="name">Intrastat extended</field>
</record>
</data>
</openerp>
@@ -0,0 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_l10n_be_intrastat_transaction","Intrastat transaction type","model_l10n_be_intrastat_transaction",,1,0,0,0
"access_l10n_be_intrastat_transport_mode","Intrastat transport mode","model_l10n_be_intrastat_transport_mode",,1,0,0,0
"access_l10n_be_intrastat_region","Intrastat regions","model_l10n_be_intrastat_region",,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_l10n_be_intrastat_transaction Intrastat transaction type model_l10n_be_intrastat_transaction 1 0 0 0
3 access_l10n_be_intrastat_transport_mode Intrastat transport mode model_l10n_be_intrastat_transport_mode 1 0 0 0
4 access_l10n_be_intrastat_region Intrastat regions model_l10n_be_intrastat_region 1 0 0 0
@@ -0,0 +1,22 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2014-2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import xml_decl
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_intrastat_declaration_xml" model="ir.ui.view">
<field name="name">Intrastat XML Declaration</field>
<field name="model">l10n_be_intrastat_xml.xml_decl</field>
<field name="arch" type="xml">
<form string="XML Intratstat Declaration">
<!-- Need the field in the view in order to be able to hide or show part of the view according the state -->
<field name="state" invisible="1" />
<field name="name" invisible="1"/>
<group states="draft">
<group>
<field name="month"/>
<field name="year"/>
<field name="tax_code_id"/>
</group>
<group>
<field name="arrivals"/>
<field name="dispatches"/>
</group>
</group>
<div states="download">
<h2>Intrastat Declaration</h2>
<p>Here is the XML file with your intrastat declaration: <field name="file_save" filename="name"/></p>
</div>
<footer>
<group states="draft">
<button name="create_xml" string="Create XML" type="object" default_focus="1" class="oe_highlight" />
or
<button string="Cancel" class="oe_link" special="cancel" />
</group>
<group states="download">
<button string="Close" special="cancel" />
</group>
</footer>
</form>
</field>
</record>
<record id="action_intrastat_xml_declaration" model="ir.actions.act_window">
<field name="name">Intrastat Declaration</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">l10n_be_intrastat_xml.xml_decl</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_intrastat_declaration_xml"/>
<field name="target">new</field>
</record>
<menuitem
name="Intrastat Declaration"
parent="l10n_be.menu_finance_belgian_statement"
action="action_intrastat_xml_declaration"
id="l10n_be_intrastat_xml_declaration"
groups="account.group_account_manager"/>
</data>
</openerp>
+373
View File
@@ -0,0 +1,373 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2014-2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
import xml.etree.ElementTree as ET
from collections import namedtuple
from datetime import datetime
from openerp import exceptions, SUPERUSER_ID, tools
from openerp.osv import fields, osv
from openerp.tools.translate import _
INTRASTAT_XMLNS = 'http://www.onegate.eu/2010-01-01'
class xml_decl(osv.TransientModel):
"""
Intrastat XML Declaration
"""
_name = "l10n_be_intrastat_xml.xml_decl"
_description = 'Intrastat XML Declaration'
def _get_tax_code(self, cr, uid, context=None):
obj_tax_code = self.pool.get('account.tax.code')
obj_user = self.pool.get('res.users')
company_id = obj_user.browse(cr, uid, uid, context=context).company_id.id
tax_code_ids = obj_tax_code.search(cr, uid, [('company_id', '=', company_id),
('parent_id', '=', False)],
context=context)
return tax_code_ids and tax_code_ids[0] or False
def _get_def_monthyear(self, cr, uid, context=None):
td = datetime.strptime(fields.date.context_today(self, cr, uid, context=context),
tools.DEFAULT_SERVER_DATE_FORMAT).date()
return td.strftime('%Y'), td.strftime('%m')
def _get_def_month(self, cr, uid, context=None):
return self._get_def_monthyear(cr, uid, context=context)[1]
def _get_def_year(self, cr, uid, context=None):
return self._get_def_monthyear(cr, uid, context=context)[0]
_columns = {
'name': fields.char('File Name'),
'month': fields.selection([('01','January'), ('02','February'), ('03','March'),
('04','April'), ('05','May'), ('06','June'), ('07','July'),
('08','August'), ('09','September'), ('10','October'),
('11','November'), ('12','December')], 'Month', required=True),
'year': fields.char('Year', size=4, required=True),
'tax_code_id': fields.many2one('account.tax.code', 'Company Tax Chart',
domain=[('parent_id', '=', False)], required=True),
'arrivals': fields.selection([('be-exempt', 'Exempt'),
('be-standard', 'Standard'),
('be-extended', 'Extended')],
'Arrivals', required=True),
'dispatches': fields.selection([('be-exempt', 'Exempt'),
('be-standard', 'Standard'),
('be-extended', 'Extended')],
'Dispatches', required=True),
'file_save': fields.binary('Intrastat Report File', readonly=True),
'state': fields.selection([('draft', 'Draft'), ('download', 'Download')], string="State"),
}
_defaults = {
'arrivals': 'be-standard',
'dispatches': 'be-standard',
'name': 'intrastat.xml',
'tax_code_id': _get_tax_code,
'month': _get_def_month,
'year': _get_def_year,
'state': 'draft',
}
def _company_warning(self, cr, uid, translated_msg, context=None):
""" Raise a error with custom message, asking user to configure company settings """
xmlid_mod = self.pool['ir.model.data']
action_id = xmlid_mod.xmlid_to_res_id(cr, uid, 'base.action_res_company_form')
raise exceptions.RedirectWarning(
translated_msg, action_id, _('Go to company configuration screen'))
def create_xml(self, cr, uid, ids, context=None):
"""Creates xml that is to be exported and sent to estate for partner vat intra.
:return: Value for next action.
:rtype: dict
"""
decl_datas = self.browse(cr, uid, ids[0])
company = decl_datas.tax_code_id.company_id
if not (company.partner_id and company.partner_id.country_id and
company.partner_id.country_id.id):
self._company_warning(
cr, uid,
_('The country of your company is not set, '
'please make sure to configure it first.'),
context=context)
kbo = company.company_registry
if not kbo:
self._company_warning(
cr, uid,
_('The registry number of your company is not set, '
'please make sure to configure it first.'),
context=context)
if len(decl_datas.year) != 4:
raise exceptions.Warning(_('Year must be 4 digits number (YYYY)'))
#Create root declaration
decl = ET.Element('DeclarationReport')
decl.set('xmlns', INTRASTAT_XMLNS)
#Add Administration elements
admin = ET.SubElement(decl, 'Administration')
fromtag = ET.SubElement(admin, 'From')
fromtag.text = kbo
fromtag.set('declarerType', 'KBO')
ET.SubElement(admin, 'To').text = "NBB"
ET.SubElement(admin, 'Domain').text = "SXX"
if decl_datas.arrivals == 'be-standard':
decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
dispatchmode=False, extendedmode=False, context=context))
elif decl_datas.arrivals == 'be-extended':
decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
dispatchmode=False, extendedmode=True, context=context))
if decl_datas.dispatches == 'be-standard':
decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
dispatchmode=True, extendedmode=False, context=context))
elif decl_datas.dispatches == 'be-extended':
decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
dispatchmode=True, extendedmode=True, context=context))
#Get xml string with declaration
data_file = ET.tostring(decl, encoding='UTF-8', method='xml')
#change state of the wizard
self.write(cr, uid, ids,
{'name': 'intrastat_%s%s.xml' % (decl_datas.year, decl_datas.month),
'file_save': base64.encodestring(data_file),
'state': 'download'},
context=context)
return {
'name': _('Save'),
'context': context,
'view_type': 'form',
'view_mode': 'form',
'res_model': 'l10n_be_intrastat_xml.xml_decl',
'type': 'ir.actions.act_window',
'target': 'new',
'res_id': ids[0],
}
def _get_lines(self, cr, uid, ids, decl_datas, company, dispatchmode=False,
extendedmode=False, context=None):
intrastatcode_mod = self.pool['report.intrastat.code']
invoiceline_mod = self.pool['account.invoice.line']
product_mod = self.pool['product.product']
region_mod = self.pool['l10n_be_intrastat.region']
warehouse_mod = self.pool['stock.warehouse']
if dispatchmode:
mode1 = 'out_invoice'
mode2 = 'in_refund'
declcode = "29"
else:
mode1 = 'in_invoice'
mode2 = 'out_refund'
declcode = "19"
decl = ET.Element('Report')
if not extendedmode:
decl.set('code', 'EX%sS' % declcode)
else:
decl.set('code', 'EX%sE' % declcode)
decl.set('date', '%s-%s' % (decl_datas.year, decl_datas.month))
datas = ET.SubElement(decl, 'Data')
if not extendedmode:
datas.set('form', 'EXF%sS' % declcode)
else:
datas.set('form', 'EXF%sE' % declcode)
datas.set('close', 'true')
intrastatkey = namedtuple("intrastatkey",
['EXTRF', 'EXCNT', 'EXTTA', 'EXREG',
'EXGO', 'EXTPC', 'EXDELTRM'])
entries = {}
sqlreq = """
select
inv_line.id
from
account_invoice_line inv_line
join account_invoice inv on inv_line.invoice_id=inv.id
left join res_country on res_country.id = inv.intrastat_country_id
left join res_partner on res_partner.id = inv.partner_id
left join res_country countrypartner on countrypartner.id = res_partner.country_id
join product_product on inv_line.product_id=product_product.id
join product_template on product_product.product_tmpl_id=product_template.id
where
inv.state in ('open','paid')
and inv.company_id=%s
and not product_template.type='service'
and (res_country.intrastat=true or (inv.intrastat_country_id is null
and countrypartner.intrastat=true))
and ((res_country.code is not null and not res_country.code=%s)
or (res_country.code is null and countrypartner.code is not null
and not countrypartner.code=%s))
and inv.type in (%s, %s)
and to_char(inv.date_invoice, 'YYYY')=%s
and to_char(inv.date_invoice, 'MM')=%s
"""
cr.execute(sqlreq, (company.id, company.partner_id.country_id.code,
company.partner_id.country_id.code, mode1, mode2,
decl_datas.year, decl_datas.month))
lines = cr.fetchall()
invoicelines_ids = [rec[0] for rec in lines]
invoicelines = invoiceline_mod.browse(cr, uid, invoicelines_ids, context=context)
for inv_line in invoicelines:
#Check type of transaction
if inv_line.invoice_id.intrastat_transaction_id:
extta = inv_line.invoice_id.intrastat_transaction_id.code
else:
extta = "1"
#Check country
if inv_line.invoice_id.intrastat_country_id:
excnt = inv_line.invoice_id.intrastat_country_id.code
else:
excnt = inv_line.invoice_id.partner_id.country_id.code
#Check region
#If purchase, comes from purchase order, linked to a location,
#which is linked to the warehouse
#if sales, the sale order is linked to the warehouse
#if sales, from a delivery order, linked to a location,
#which is linked to the warehouse
#If none found, get the company one.
exreg = None
if inv_line.invoice_id.type in ('in_invoice', 'in_refund'):
#comes from purchase
POL = self.pool['purchase.order.line']
poline_ids = POL.search(
cr, uid, [('invoice_lines', 'in', inv_line.id)], context=context)
if poline_ids:
purchaseorder = POL.browse(cr, uid, poline_ids[0], context=context).order_id
region_id = warehouse_mod.get_regionid_from_locationid(
cr, uid, purchaseorder.location_id.id, context=context)
if region_id:
exreg = region_mod.browse(cr, uid, region_id).code
elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'):
#comes from sales
soline_ids = self.pool['sale.order.line'].search(
cr, uid, [('invoice_lines', 'in', inv_line.id)], context=context)
if soline_ids:
saleorder = self.pool['sale.order.line'].browse(
cr, uid, soline_ids[0], context=context).order_id
if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id:
exreg = region_mod.browse(
cr, uid, saleorder.warehouse_id.region_id.id, context=context).code
if not exreg:
if company.region_id:
exreg = company.region_id.code
else:
self._company_warning(
cr, uid,
_('The Intrastat Region of the selected company is not set, '
'please make sure to configure it first.'),
context=context)
#Check commodity codes
intrastat_id = product_mod.get_intrastat_recursively(
cr, uid, inv_line.product_id.id, context=context)
if intrastat_id:
exgo = intrastatcode_mod.browse(cr, uid, intrastat_id, context=context).name
else:
raise exceptions.Warning(
_('Product "%s" has no intrastat code, please configure it') %
inv_line.product_id.display_name)
#In extended mode, 2 more fields required
if extendedmode:
#Check means of transport
if inv_line.invoice_id.transport_mode_id:
extpc = inv_line.invoice_id.transport_mode_id.code
elif company.transport_mode_id:
extpc = company.transport_mode_id.code
else:
self._company_warning(
cr, uid,
_('The default Intrastat transport mode of your company '
'is not set, please make sure to configure it first.'),
context=context)
#Check incoterm
if inv_line.invoice_id.incoterm_id:
exdeltrm = inv_line.invoice_id.incoterm_id.code
elif company.incoterm_id:
exdeltrm = company.incoterm_id.code
else:
self._company_warning(
cr, uid,
_('The default Incoterm of your company is not set, '
'please make sure to configure it first.'),
context=context)
else:
extpc = ""
exdeltrm = ""
linekey = intrastatkey(EXTRF=declcode, EXCNT=excnt,
EXTTA=extta, EXREG=exreg, EXGO=exgo,
EXTPC=extpc, EXDELTRM=exdeltrm)
#We have the key
#calculate amounts
if inv_line.price_unit and inv_line.quantity:
amount = inv_line.price_unit * inv_line.quantity
else:
amount = 0
if (not inv_line.uos_id.category_id
or not inv_line.product_id.uom_id.category_id
or inv_line.uos_id.category_id.id != inv_line.product_id.uom_id.category_id.id):
weight = inv_line.product_id.weight_net * inv_line.quantity
else:
weight = (inv_line.product_id.weight_net *
inv_line.quantity * inv_line.uos_id.factor)
if (not inv_line.uos_id.category_id or not inv_line.product_id.uom_id.category_id
or inv_line.uos_id.category_id.id != inv_line.product_id.uom_id.category_id.id):
supply_units = inv_line.quantity
else:
supply_units = inv_line.quantity * inv_line.uos_id.factor
amounts = entries.setdefault(linekey, (0, 0, 0))
amounts = (amounts[0] + amount, amounts[1] + weight, amounts[2] + supply_units)
entries[linekey] = amounts
numlgn = 0
for linekey in entries:
numlgn += 1
amounts = entries[linekey]
item = ET.SubElement(datas, 'Item')
self._set_Dim(item, 'EXSEQCODE', unicode(numlgn))
self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF))
self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT))
self._set_Dim(item, 'EXTTA', unicode(linekey.EXTTA))
self._set_Dim(item, 'EXREG', unicode(linekey.EXREG))
self._set_Dim(item, 'EXTGO', unicode(linekey.EXGO))
if extendedmode:
self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC))
self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM))
self._set_Dim(item, 'EXTXVAL', unicode(round(amounts[0], 0)).replace(".", ","))
self._set_Dim(item, 'EXWEIGHT', unicode(round(amounts[1], 0)).replace(".", ","))
self._set_Dim(item, 'EXUNITS', unicode(round(amounts[2], 0)).replace(".", ","))
if numlgn == 0:
#no datas
datas.set('action', 'nihil')
return decl
def _set_Dim(self, item, prop, value):
dim = ET.SubElement(item, 'Dim')
dim.set('prop', prop)
dim.text = value
@@ -33,7 +33,7 @@
</div>
<div class="row">
<div class="col-xs-4">
<div>
<table class="table table-bordered">
<tr>
<td>
@@ -44,16 +44,18 @@
<span>Niveau :</span><br/>
<span>Coef :</span><br/>
<span>Entrée :</span><br/>
<span>Sortie :</span><br/>
<span>ORG. S.S :</span>
</td>
<td>
<span t-esc="o.employee_id.id or '_'"/><br/>
<span t-esc="o.employee_id.ssnid or '_'"/><br/>
<span t-esc="o.contract_id.job_id or '_'"/><br/>
<span t-esc="o.contract_id.job_id.name or '_'"/><br/>
<span t-esc="o.contract_id.qualif or '_'"/><br/>
<span t-esc="o.contract_id.niveau or '_'"/><br/>
<span t-esc="o.contract_id.coef or '_'"/><br/>
<span t-esc="o.contract_id.date_start or '_'"/> Sortie :<br/>
<span t-esc="o.contract_id.date_end or ''"/><br/>
<span t-esc="o.contract_id.date_start or '_'"/><br/>
<span t-esc="o.contract_id.date_end or '_'"/><br/>
<span t-esc="o.employee_id.company_id.org_ss or '_'"/>
</td>
</tr>
@@ -63,18 +65,22 @@
<table class="table table-condensed">
<thead>
<tr>
<th>Code</th>
<th>Désignation</th>
<th>Quantité / Base</th>
<th>Taux</th>
<th>Montant</th>
<th colspan="2">Charges Patronales</th>
<th>Taux Charges Patronales</th>
<th>Montant Charges Patronales</th>
</tr>
</thead>
<tbody>
<tr t-foreach="get_payslip_lines(o.line_ids)" t-as="p">
<td>
<span t-esc="p.code or ''"/>
<span t-esc="p.name or ''" class="col-xs-offset-1"/>
</td>
<td>
<span t-esc="p.name or ''"/>
</td>
<td>
<t t-if="p.amount_select== 'percentage'">
@@ -94,7 +100,9 @@
</td>
<td>
<span t-esc="get_employer_line(o, p) and get_employer_line(o, p).rate or ''"/>
<span t-esc="get_employer_line(o,p) and get_employer_line(o,p).total or ''"/>
</td>
<td>
<span t-esc="get_employer_line(o,p) and get_employer_line(o,p).total or ''"/>
</td>
</tr>
</tbody>
+2 -2
View File
@@ -396,8 +396,8 @@ class mail_compose_message(osv.TransientModel):
values.setdefault('attachment_ids', list()).append(ir_attach_obj.create(cr, uid, data_attach, context=context))
else:
default_context = dict(context, default_composition_mode=composition_mode, default_model=model, default_res_id=res_id)
default_values = self.default_get(cr, uid, ['composition_mode', 'model', 'res_id', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'], context=default_context)
values = dict((key, default_values[key]) for key in ['subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values)
default_values = self.default_get(cr, uid, ['composition_mode', 'model', 'res_id', 'parent_id', 'partner_ids', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'], context=default_context)
values = dict((key, default_values[key]) for key in ['subject', 'body', 'partner_ids', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values)
if values.get('body_html'):
values['body'] = values.pop('body_html')
+1
View File
@@ -800,6 +800,7 @@ class pos_order(osv.osv):
picking_id = picking_obj.create(cr, uid, {
'origin': order.name,
'partner_id': addr.get('delivery',False),
'date_done' : order.date_order,
'picking_type_id': picking_type.id,
'company_id': order.company_id.id,
'move_type': 'direct',
+1 -1
View File
@@ -244,7 +244,7 @@ FaceTime HD Camera, 1.2 MP Photos</field>
<field name="uom_id" ref="product_uom_unit"/>
<field name="uom_po_id" ref="product_uom_unit"/>
<field name="description">Custom computer assembled on order based on customer's requirement.</field>
<field name="default_code">B3423</field>
<field name="default_code">B3424</field>
</record>
<record id="product_product_6" model="product.product">
@@ -12,6 +12,7 @@ access_product_pricelist_type_user,product.pricelist.type.user,model_product_pri
access_product_pricelist_user,product.pricelist.user,model_product_pricelist,base.group_user,1,0,0,0
access_product_pricelist_version_user,product.pricelist.version.user,model_product_pricelist_version,base.group_user,1,0,0,0
access_product_pricelist_item_user,product.pricelist.item.user,model_product_pricelist_item,base.group_user,1,0,0,0
access_product_pricelist_item_sale_manager,product.pricelist.item salemanager,product.model_product_pricelist_item,base.group_sale_manager,1,1,1,1
access_product_pricelist_type_partner_manager,product.pricelist.type partner manager,model_product_pricelist_type,base.group_partner_manager,1,0,0,0
access_product_pricelist_partner_manager,product.pricelist partner manager,model_product_pricelist,base.group_partner_manager,1,0,0,0
access_product_product_employee,product.product employee,model_product_product,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
12 access_product_pricelist_user product.pricelist.user model_product_pricelist base.group_user 1 0 0 0
13 access_product_pricelist_version_user product.pricelist.version.user model_product_pricelist_version base.group_user 1 0 0 0
14 access_product_pricelist_item_user product.pricelist.item.user model_product_pricelist_item base.group_user 1 0 0 0
15 access_product_pricelist_item_sale_manager product.pricelist.item salemanager product.model_product_pricelist_item base.group_sale_manager 1 1 1 1
16 access_product_pricelist_type_partner_manager product.pricelist.type partner manager model_product_pricelist_type base.group_partner_manager 1 0 0 0
17 access_product_pricelist_partner_manager product.pricelist partner manager model_product_pricelist base.group_partner_manager 1 0 0 0
18 access_product_product_employee product.product employee model_product_product base.group_user 1 0 0 0
+1
View File
@@ -60,6 +60,7 @@ class stock_move(osv.osv):
def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
if move.purchase_line_id:
invoice_line_vals['purchase_line_id'] = move.purchase_line_id.id
invoice_line_vals['account_analytic_id'] = move.purchase_line_id.account_analytic_id.id or False
invoice_line_id = super(stock_move, self)._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
if move.purchase_line_id:
purchase_line = move.purchase_line_id
@@ -39,6 +39,7 @@ class res_country(osv.osv):
class report_intrastat_code(osv.osv):
_name = "report.intrastat.code"
_description = "Intrastat code"
_translate = False
_columns = {
'name': fields.char('Intrastat Code'),
'description': fields.char('Description'),
+1 -1
View File
@@ -99,7 +99,7 @@ class crm_make_sale(osv.osv_memory):
'pricelist_id': pricelist,
'partner_invoice_id': partner_addr['invoice'],
'partner_shipping_id': partner_addr['delivery'],
'date_order': fields.date.context_today(self,cr,uid,context=context),
'date_order': fields.datetime.now(),
'fiscal_position': fpos,
'payment_term':payment_term,
'opportunity_id': case.id,
+1
View File
@@ -44,6 +44,7 @@ synchronization with other companies.
'website': 'https://www.odoo.com',
'data': [
'security/share_security.xml',
'security/ir.model.access.csv',
'res_users_view.xml',
'wizard/share_wizard_view.xml',
'views/share.xml',
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
"access_ir_ui_menu_group_shared","ir_ui_menu group_shared","base.model_ir_ui_menu",group_shared,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_ir_ui_menu_group_shared ir_ui_menu group_shared base.model_ir_ui_menu group_shared 1 0 0 0
+8
View File
@@ -6,5 +6,13 @@
<field name="parent_id" ref="base.module_category_tools"/>
<field name="sequence">26</field>
</record>
<record id="group_shared" model="res.groups">
<field name="name">Shared Group</field>
<field name="comment">
This group is a template group for shared users, giving basic access needs, the menus for instance.</field>
<field name="category_id" ref="module_category_share"/>
<field name="share">1</field>
</record>
</data>
</openerp>
+8
View File
@@ -7,5 +7,13 @@
<script type="text/javascript" src="/share/static/src/js/share.js"></script>
</xpath>
</template>
<template id="webclient_bootstrap" name="Webclient Bootstrap - Share" inherit_id="web.webclient_bootstrap">
<xpath expr="//nav[@id='oe_main_menu_navbar']" position="attributes">
<attribute name="groups">base.group_user,base.group_portal,share.group_shared</attribute>
</xpath>
<xpath expr="//div[@class='openerp openerp_webclient_container']//div" position="attributes">
<attribute name="groups">base.group_user,base.group_portal,share.group_shared</attribute>
</xpath>
</template>
</data>
</openerp>
+12 -5
View File
@@ -212,8 +212,15 @@ class share_wizard(osv.TransientModel):
def _create_share_group(self, cr, uid, wizard_data, context=None):
group_obj = self.pool.get('res.groups')
share_group_name = '%s: %s (%d-%s)' %('Shared', wizard_data.name, uid, time.time())
values = {'name': share_group_name, 'share': True}
try:
implied_group_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'share', 'group_shared')[1]
except ValueError:
implied_group_id = None
if implied_group_id:
values['implied_ids'] = [(4, implied_group_id)]
# create share group without putting admin in it
return group_obj.create(cr, UID_ROOT, {'name': share_group_name, 'share': True}, {'noadmin': True})
return group_obj.create(cr, UID_ROOT, values, {'noadmin': True})
def _create_new_share_users(self, cr, uid, wizard_data, group_id, context=None):
"""Create one new res.users record for each email address provided in
@@ -486,7 +493,7 @@ class share_wizard(osv.TransientModel):
# already granted
for dummy, model in fields_relations:
# mail.message is transversal: it should not received directly the access rights
if model.model in ['mail.message']: continue
if model.model in ['mail.message', 'mail.notification']: continue
values = {
'name': _('Copied access for sharing'),
'group_id': group_id,
@@ -618,7 +625,7 @@ class share_wizard(osv.TransientModel):
if domain:
for rel_field, model in fields_relations:
# mail.message is transversal: it should not received directly the access rights
if model.model in ['mail.message']: continue
if model.model in ['mail.message', 'mail.notification']: continue
related_domain = []
if not rel_field: continue
for element in domain:
@@ -891,8 +898,8 @@ class share_result_line(osv.osv_memory):
for this in self.browse(cr, uid, ids, context=context):
data = dict(dbname=cr.dbname, login=this.login, password=this.password)
if this.share_wizard_id and this.share_wizard_id.action_id:
data['action_id'] = this.share_wizard_id.action_id.id
this = this.with_context(share_url_template_hash_arguments=['action_id'])
data['action'] = this.share_wizard_id.action_id.id
this = this.with_context(share_url_template_hash_arguments=['action'])
result[this.id] = this.share_wizard_id.share_url_template() % data
return result
@@ -25,7 +25,6 @@ access_stock_lines_date_user,report.stock.lines.date user,model_report_stock_lin
access_stock_lines_date_manager,report.stock.lines.date manager,model_report_stock_lines_date,stock.group_stock_manager,1,1,1,1
access_product_product_stock_user,product_product_stock_user,product.model_product_product,stock.group_stock_user,1,1,0,0
access_product_template_stock_user,product.template stock user,product.model_product_template,stock.group_stock_user,1,1,0,0
access_product_pricelist_item_sale_manager,product.pricelist.item salemanager,product.model_product_pricelist_item,base.group_sale_manager,1,1,1,1
access_product_uom_categ_stock_manager,product.uom.categ stock_manager,product.model_product_uom_categ,stock.group_stock_manager,1,1,1,1
access_product_uom_stock_manager,product.uom stock_manager,product.model_product_uom,stock.group_stock_manager,1,1,1,1
access_product_ul_stock_manager,product.ul stock_manager,product.model_product_ul,stock.group_stock_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
25 access_stock_lines_date_manager report.stock.lines.date manager model_report_stock_lines_date stock.group_stock_manager 1 1 1 1
26 access_product_product_stock_user product_product_stock_user product.model_product_product stock.group_stock_user 1 1 0 0
27 access_product_template_stock_user product.template stock user product.model_product_template stock.group_stock_user 1 1 0 0
access_product_pricelist_item_sale_manager product.pricelist.item salemanager product.model_product_pricelist_item base.group_sale_manager 1 1 1 1
28 access_product_uom_categ_stock_manager product.uom.categ stock_manager product.model_product_uom_categ stock.group_stock_manager 1 1 1 1
29 access_product_uom_stock_manager product.uom stock_manager product.model_product_uom stock.group_stock_manager 1 1 1 1
30 access_product_ul_stock_manager product.ul stock_manager product.model_product_ul stock.group_stock_manager 1 1 1 1
+35 -27
View File
@@ -420,10 +420,12 @@ class stock_quant(osv.osv):
self._quant_reconcile_negative(cr, uid, quant, move, context=context)
def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None):
context=context or {}
vals = {'location_id': location_dest_id.id,
'history_ids': [(4, move.id)],
'package_id': dest_package_id,
'reservation_id': False}
if not context.get('entire_pack'):
vals.update({'package_id': dest_package_id})
self.write(cr, SUPERUSER_ID, [q.id for q in quants], vals, context=context)
def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain_list=[], restrict_lot_id=False, restrict_partner_id=False, context=None):
@@ -1061,7 +1063,7 @@ class stock_picking(osv.osv):
'product_qty': 1.0,
'location_id': pack.location_id.id,
'location_dest_id': quants_suggested_locations[pack_quants[0]],
'owner_id': picking.owner_id.id,
'owner_id': pack.owner_id.id,
})
#remove the quants inside the package so that they are excluded from the rest of the computation
for quant in pack_quants:
@@ -2359,16 +2361,17 @@ class stock_move(osv.osv):
dom = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, record.qty, domain=dom, prefered_domain_list=prefered_domain_list,
restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
if ops.result_package_id.id:
#if a result package is given, all quants go there
quant_dest_package_id = ops.result_package_id.id
elif ops.product_id and ops.package_id:
#if a package and a product is given, we will remove quants from the pack.
quant_dest_package_id = False
if ops.product_id:
#If a product is given, the result is always put immediately in the result package (if it is False, they are without package)
quant_dest_package_id = ops.result_package_id.id
ctx = context
else:
#otherwise we keep the current pack of the quant, which may mean None
quant_dest_package_id = ops.package_id.id
quant_obj.quants_move(cr, uid, quants, move, ops.location_dest_id, location_from=ops.location_id, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=quant_dest_package_id, context=context)
# When a pack is moved entirely, the quants should not be written anything for the destination package
quant_dest_package_id = False
ctx = context.copy()
ctx['entire_pack'] = True
quant_obj.quants_move(cr, uid, quants, move, ops.location_dest_id, location_from=ops.location_id, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=quant_dest_package_id, context=ctx)
# Handle pack in pack
if not ops.product_id and ops.package_id and ops.result_package_id.id != ops.package_id.parent_id.id:
self.pool.get('stock.quant.package').write(cr, SUPERUSER_ID, [ops.package_id.id], {'parent_id': ops.result_package_id.id}, context=context)
@@ -3711,10 +3714,29 @@ class stock_package(osv.osv):
"""Returns packages from quants for store"""
res = set()
for quant in self.browse(cr, uid, ids, context=context):
if quant.package_id:
res.add(quant.package_id.id)
pack = quant.package_id
while pack:
res.add(pack.id)
pack = pack.parent_id
return list(res)
def _get_package_info(self, cr, uid, ids, name, args, context=None):
quant_obj = self.pool.get("stock.quant")
default_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
res = dict((res_id, {'location_id': False, 'company_id': default_company_id, 'owner_id': False}) for res_id in ids)
for pack in self.browse(cr, uid, ids, context=context):
quants = quant_obj.search(cr, uid, [('package_id', 'child_of', pack.id)], context=context)
if quants:
quant = quant_obj.browse(cr, uid, quants[0], context=context)
res[pack.id]['location_id'] = quant.location_id.id
res[pack.id]['owner_id'] = quant.owner_id.id
res[pack.id]['company_id'] = quant.company_id.id
else:
res[pack.id]['location_id'] = False
res[pack.id]['owner_id'] = False
res[pack.id]['company_id'] = False
return res
def _get_packages_to_relocate(self, cr, uid, ids, context=None):
res = set()
for pack in self.browse(cr, uid, ids, context=context):
@@ -3723,20 +3745,6 @@ class stock_package(osv.osv):
res.add(pack.parent_id.id)
return list(res)
def _get_package_info(self, cr, uid, ids, name, args, context=None):
default_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
res = dict((res_id, {'location_id': False, 'company_id': default_company_id, 'owner_id': False}) for res_id in ids)
for pack in self.browse(cr, uid, ids, context=context):
if pack.quant_ids:
res[pack.id]['location_id'] = pack.quant_ids[0].location_id.id
res[pack.id]['owner_id'] = pack.quant_ids[0].owner_id and pack.quant_ids[0].owner_id.id or False
res[pack.id]['company_id'] = pack.quant_ids[0].company_id.id
elif pack.children_ids:
res[pack.id]['location_id'] = pack.children_ids[0].location_id and pack.children_ids[0].location_id.id or False
res[pack.id]['owner_id'] = pack.children_ids[0].owner_id and pack.children_ids[0].owner_id.id or False
res[pack.id]['company_id'] = pack.children_ids[0].company_id and pack.children_ids[0].company_id.id or False
return res
_columns = {
'name': fields.char('Package Reference', select=True, copy=False),
'complete_name': fields.function(_complete_name, type='char', string="Package Name",),
+1 -1
View File
@@ -106,7 +106,7 @@
</div>
<group>
<group>
<field name="location_id"/>
<field name="location_id" domain="[('usage','not in', ['supplier','production'])]"/>
<field name="filter" string="Inventory of" widget='radio' attrs="{'readonly': [('state', '!=', 'draft')]}"/>
</group>
<group>
@@ -0,0 +1 @@
from . import test_invoicing
@@ -0,0 +1,60 @@
# Author: Leonardo Pistone
# Copyright 2015 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from openerp.tests.common import TransactionCase
class TestCreateInvoice(TransactionCase):
def setUp(self):
super(TestCreateInvoice, self).setUp()
self.Wizard = self.env['stock.invoice.onshipping']
self.customer = self.env.ref('base.res_partner_3')
product = self.env.ref('product.product_product_36')
dropship_route = self.env.ref('stock_dropshipping.route_drop_shipping')
self.so = self.env['sale.order'].create({
'partner_id': self.customer.id,
})
self.sol = self.env['sale.order.line'].create({
'name': '/',
'order_id': self.so.id,
'product_id': product.id,
'route_id': dropship_route.id,
})
def test_po_on_delivery_creates_correct_invoice(self):
self.so.action_button_confirm()
po = self.so.procurement_group_id.procurement_ids.purchase_id
self.assertTrue(po)
po.invoice_method = 'picking'
po.signal_workflow('purchase_confirm')
picking = po.picking_ids
self.assertEqual(1, len(picking))
picking.action_done()
wizard = self.Wizard.with_context({
'active_id': picking.id,
'active_ids': [picking.id],
}).create({})
invoice_ids = wizard.create_invoice()
invoices = self.env['account.invoice'].browse(invoice_ids)
self.assertEqual(1, len(invoices))
self.assertEqual(invoices.type, 'in_invoice')
self.assertEqual(invoices, po.invoice_ids)
+2 -2
View File
@@ -1255,7 +1255,7 @@ class Action(http.Controller):
except Exception:
action_id = 0 # force failed read
base_action = Actions.read([action_id], ['type'], request.context)
base_action = Actions.read([action_id], ['name', 'type'], request.context)
if base_action:
ctx = request.context
action_type = base_action[0]['type']
@@ -1265,7 +1265,7 @@ class Action(http.Controller):
ctx.update(additional_context)
action = request.session.model(action_type).read([action_id], False, ctx)
if action:
value = clean_action(action[0])
value = clean_action(dict(action[0], **base_action[0]))
return value
@http.route('/web/action/run', type='json', auth="user")
+1 -1
View File
@@ -177,7 +177,7 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
return value.format(date_format + ' ' + time_format);
case 'date':
if (typeof(value) == "string")
value = moment(instance.web.str_to_date(value.substring(0,10)));
value = moment(instance.web.auto_str_to_date(value));
else {
value = moment(value);
}
+1 -1
View File
@@ -478,7 +478,7 @@ openerp.PropertiesMixin = _.extend({}, openerp.EventDispatcherMixin, {
var tmp = self.__getterSetterInternalMap[key];
if (tmp === val)
return;
if (self.field && self.field.type === 'float' && tmp && val){
if (key === 'value' && self.field && self.field.type === 'float' && tmp && val){
var precision = self.field.digits ? self.field.digits[1] : 2;
if (openerp.web.float_is_zero(tmp - val, precision))
return;
+4 -2
View File
@@ -448,8 +448,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return "";
};
self._onchange_fields = [];
self._onchange_specs = {};
_.each(this.fields, function(field, name) {
self._onchange_fields.push(name);
self._onchange_specs[name] = find(name, field.node);
_.each(field.field.views, function(view) {
_.each(view.fields, function(_, subname) {
@@ -486,7 +488,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var change_spec = widget ? onchange_specs[widget.name] : null;
if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) {
var ids = [],
trigger_field_name = widget ? widget.name : false,
trigger_field_name = widget ? widget.name : self._onchange_fields,
values = self._get_onchange_values(),
context = new instance.web.CompoundContext(self.dataset.get_context());
@@ -1157,7 +1159,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
},
build_eval_context: function() {
var a_dataset = this.dataset;
return new instance.web.CompoundContext(this._build_view_fields_values(), a_dataset.get_context());
return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values());
},
});
+7 -3
View File
@@ -356,7 +356,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
//Sort
var default_order = this.fields_view.arch.attrs.default_order,
unsorted = !this.dataset._sort.length;
if (unsorted && default_order) {
if (unsorted && default_order && !this.grouped) {
this.dataset.set_sort(default_order.split(','));
}
@@ -2230,7 +2230,7 @@ instance.web.list.Column = instance.web.Class.extend({
}
if (attrs.invisible) { return ''; }
if (!(row_data[this.id] && row_data[this.id].value)) {
if (!row_data[this.id]) {
return options.value_if_empty === undefined
? ''
: options.value_if_empty;
@@ -2299,8 +2299,12 @@ instance.web.list.Binary = instance.web.list.Column.extend({
_format: function (row_data, options) {
var text = _t("Download");
var value = row_data[this.id].value;
if (!value) {
return options.value_if_empty || '';
}
var download_url;
if (value && value.substr(0, 10).indexOf(' ') == -1) {
if (value.substr(0, 10).indexOf(' ') == -1) {
download_url = "data:application/octet-stream;base64," + value;
} else {
download_url = instance.session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
+3
View File
@@ -841,6 +841,9 @@ instance.web.ViewManager = instance.web.Widget.extend({
if (_.isString(groupby)) {
groupby = [groupby];
}
if (!controller.grouped && !_.isEmpty(groupby)){
self.dataset.set_sort([]);
}
$.when(controller.do_search(results.domain, results.context, groupby || [])).then(function() {
self.active_search.resolve();
});
+3 -3
View File
@@ -60,6 +60,9 @@ class contactus(http.Controller):
post_description = [] # Info to add after the message
values = {}
values['medium_id'] = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'crm.crm_medium_website')
values['team_id'] = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'website.salesteam_website_sales')
for field_name, field_value in kwargs.items():
if hasattr(field_value, 'filename'):
post_file.append(field_value)
@@ -77,9 +80,6 @@ class contactus(http.Controller):
values = dict(values, error=error, kwargs=kwargs.items())
return request.website.render(kwargs.get("view_from", "website.contactus"), values)
values['medium_id'] = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'crm.crm_medium_website')
values['team_id'] = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'website.salesteam_website_sales')
# description is required, so it is always already initialized
if post_description:
values['description'] += dict_to_str(_("Custom Fields: "), post_description)
+2 -2
View File
@@ -53,14 +53,14 @@ class WebsiteCustomer(http.Controller):
if country_id:
domain += [('country_id', '=', country_id)]
if not any(x['country_id'][0] == country_id for x in countries):
if not any(x['country_id'][0] == country_id for x in countries if x['country_id']):
country = country_obj.read(cr, uid, country_id, ['name'], context)
if country:
countries.append({
'country_id_count': 0,
'country_id': (country_id, country['name'])
})
countries.sort(key=lambda d: d['country_id'][1])
countries.sort(key=lambda d: d['country_id'] and d['country_id'][1])
curr_country = country_obj.browse(cr, uid, country_id, context)
countries.insert(0, {
@@ -49,7 +49,7 @@
},
{
title: "Check that the cart contains exactly 3 elements",
element: '#top_menu .my_cart_quantity:containsExact(3)',
element: 'a:has(.my_cart_quantity):containsExact(3)',
},
{
title: "Modify the cart to add 1 unit of `VIP` ticket type",
@@ -58,7 +58,7 @@
},
{
title: "Now click on `Process Checkout`",
waitFor: '#top_menu .my_cart_quantity:contains(4)',
waitFor: 'a:has(.my_cart_quantity):contains(4)',
element: '.btn-primary:contains("Process Checkout")'
},
{
+3
View File
@@ -852,6 +852,9 @@ class website_sale(http.Controller):
composer_values.update(composer_obj.onchange_template_id(cr, SUPERUSER_ID, None, *template_values, context=context).get('value', {}))
if not composer_values.get('email_from') and uid == request.website.user_id.id:
composer_values['email_from'] = request.website.user_id.company_id.email
for key in ['attachment_ids', 'partner_ids']:
if composer_values.get(key):
composer_values[key] = [(6, 0, composer_values[key])]
composer_id = composer_obj.create(cr, SUPERUSER_ID, composer_values, context=email_ctx)
composer_obj.send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx)
+48
View File
@@ -0,0 +1,48 @@
# Odoo Corporate Contributor License Agreement
## Odoo CCLA v1.0
Based on the Apache Software Foundation Corporate Contributor License Agreement v2.0, with modifications
Thank you for your interest in an Odoo S.A. (the "Project Leads") open source project. In order to clarify the intellectual property license granted with Contributions from any person or entity, the Odoo Project Leads must have a Contributor License Agreement (the "Agreement") on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of the Project, its users, and the Odoo Project Leads; it does not change your rights to use your own Contributions for any other purpose. If you have not already done so, please complete, sign and send the Agreement by:
* Scanning and emailing to cla-submission@odoo.com
**Please read this document carefully before signing and keep a copy for your records.**
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the Project. Except for the license granted herein to the Project Leads and recipients of software distributed by the Project Leads, You reserve all right, title, and interest in and to Your Contributions.
1. Definitions.
"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"Contribution" shall mean the code, documentation or any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project Leads for inclusion in, or documentation of, any of the products managed or maintained by the Project Leads (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project Leads or their representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project Leads for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to the Project Leads and to recipients of software distributed by the Project Leads a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to the Project Leads and to recipients of software distributed by the Project Leads a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation designated on Schedule A below (or in a subsequent written modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation.
5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
7. Should You wish to submit work that is not Your original creation, You may submit it to the Project Leads separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
8. It is your responsibility to notify the Project Leads when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with the Project.
```
Corporation name: __________________________________________
Corporation address: _______________________________________
_______________________________________
Country: ___________________________________________________
Point of contact: __________________________________________
Title: _____________________________________________________
Email: _____________________________________________________
Telephone: _________________________________________________
Signature: _________________________________________________
Date: ______________________________________________________
```
## Schedule A
[Initial list of designated employees. NB: authorization is not tied to particular Contributions.]
+14
View File
@@ -0,0 +1,14 @@
India, 2015-02-10
Serpent Consulting Services Pvt Ltd. agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Serpent Consulting Services Pvt Ltd. contact@serpentcs.com https://github.com/JayVora-SerpentCS
List of contributors:
Jay Vora jay.vora@serpentcs.com https://github.com/JayVora-SerpentCS
Husen Daudi husen.daudi@serpentcs.com https://github.com/hda
+24
View File
@@ -0,0 +1,24 @@
Belgium, 2015-02-11
ACSONE SA/NV agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Stéphane Bidoul stephane.bidoul@acsone.eu https://github.com/sbidoul
List of contributors:
Adrien Peiffer adrien.peiffer@acsone.eu https://github.com/adrienpeiffer
Anthony Muschang anthony.muschang@acsone.eu https://github.com/anthony-muschang
Cédric Pigeon cedric.pigeon@acsone.eu https://github.com/Cedric-Pigeon
Christelle De Coninck christelle.deconinck@acsone.eu https://github.com/chrisdec
Jonathan Nemry jonathan.nemry@acsone.eu https://github.com/JonathanNEMRY
Laetia Gangloff laetitia.gangloff@acsone.eu https://github.com/laetitia-gangloff
Laurent Mignon laurent.mignon@acsone.eu https://github.com/lmignon
Olivier Laurent olivier.laurent@acsone.eu https://github.com/Olivier-LAURENT
Philippe Schmidt philippe.schmidt@acsone.eu https://github.com/phschmidt
Stéphane Bidoul stephane.bidoul@acsone.eu https://github.com/sbidoul
+16
View File
@@ -0,0 +1,16 @@
Netherlands, 8-2-2015
BAS Solutions agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Erwin van der Ploeg erwin@bas-solutions.nl https://github.com/erwin-bas-solutions
List of contributors:
Erwin van der Ploeg erwin@bas-solutions.nl https://github.com/erwin-bas-solutions
Ruchir Shukla ruchir@bas-solutions.nl https://github.com/ruchir-bas-solutions
+28
View File
@@ -0,0 +1,28 @@
Switzerland, France, 2015-02-10
Camptocamp SA and Camptocamp SaS agrees to the terms of the Odoo Corporate
Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Luc Maurer luc.maurer@camptocamp.com https://github.com/camptocamp
List of contributors:
Luc Maurer luc.maurer@camptocamp.com https://github.com/lucmaurer
Joël Grand-Guillaume joel.grandguillaume@camptocamp.com https://github.com/jgrandguillaume
Guewen Baconnier guewen.baconnier@camptocamp.com https://github.com/guewen
Yannick Vaucher yannick.vaucher@camptocamp.com https://github.com/yvaucher
Nicolas Bessi nicolas.bessi@camptocamp.com https://github.com/nbessi
Frédéric Clementi frederic.clementi@camptocamp.com https://github.com/fclementic2c
Laurent Meuwly laurent.meuwly@camptocamp.com https://github.com/laurentmeuwly
Vincent Renaville vincet.renaville@camptocamp.com https://github.com/vrenaville
Matthieu Dietrich matthieu.dietrich@camptocamp.com https://github.com/mdietrichc2c
Alexandre Fayolle alexandre.fayolle@camptocamp.com https://github.com/gurneyalex
Leonardo Pistone leonardo.pistone@camptocamp.com https://github.com/lepistone
Romain Deheele romain.deheele@camptocamp.com https://github.com/rdeheele
Maxime Wiot maxime.wiot@camptocamp.com https://github.com/maxime-c2c
+15
View File
@@ -0,0 +1,15 @@
Germany 2015-02-11
copado MEDIA UG agrees to the terms of the Odoo Corporate
Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Mathias Neef mn@copado.de https://github.com/copado
List of contributors:
Mathias Neef mn@copado.de https://github.com/copmn
+16
View File
@@ -0,0 +1,16 @@
Hungary, 2015 febr. 09
i3 Rendszerház Kft. agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Csaba Tóth csaba.toth@i3rendszerhaz.hu https://github.com/tsabi
List of contributors:
Csaba Tóth csaba.toth@i3rendszerhaz.hu https://github.com/tsabi
Anna Szabó anna.szabo@i3rendszerhaz.hu
+29
View File
@@ -0,0 +1,29 @@
Canada, France, 8-2-2015
Savoir-faire Linux agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Maxime Chambreuil partners@savoirfairelinux.com https://github.com/savoirfairelinux
List of contributors:
Bruno Joliveau bruno.joliveau@savoirfairelinux.com https://github.com/bjoliveau
David Cormier david.cormier@savoirfairelinux.com https://github.com/cormier
David Dufresne david.dufresne@savoirfairelinux.com https://github.com/dufresnedavid
Davin Baragiotta davin.baragiotta@savoirfairelinux.com https://github.com/giotta
El Hadji Dem elhadji.dem@savoirfairelinux.com https://github.com/ehdem
Guillaume Auger guillaume.auger@savoirfairelinux.com https://github.com/jehog
Joao Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf
Jordi Riera jordi.riera@savoirfairelinux.com https://github.com/foutoucour
Julien Roux julien.roux@savoirfairelinux.com https://github.com/jrouxsfl
Loïc Faure-Lacroix loic.lacroix@savoirfairelinux.com https://github.com/llacroix
Maxime Chambreuil maxime.chambreuil@savoirfairelinux.com https://github.com/max3903
Pierre Gault pierre.gault@savoirfairelinux.com https://github.com/gaultp
Pierre Lamarche pierre.lamarche@savoirfairelinux.com https://github.com/plamarche
Sandy Carter sandy.carter@savoirfairelinux.com https://github.com/bwrsandman
Vincent Vinet vincent.vinet@savoirfairelinux.com https://github.com/veloutin
+16
View File
@@ -0,0 +1,16 @@
India, U.A.E, Singapore, 2015-02-10
Tech Receptives, Tech-Receptives Solutions Pvt. Ltd, Tech Receptives IT Solutions FZC and Tech Receptives Asia Pacific Pte Ltd agrees to the terms of the Odoo Corporate
Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Parthiv Patel parthiv@techreceptives.com https://github.com/techreceptives
List of contributors:
Parthiv Patel parthiv@techreceptives.com https://github.com/parthivgls
+18
View File
@@ -0,0 +1,18 @@
México, 9-2-2015
Vauxoo agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Moisés López moylop260@vauxoo.com https://github.com/moylop260
List of contributors:
Humberto Arocha hbto@vauxoo.com https://github.com/hbto
Javier Duran javier@vauxoo.com
Moisés López moylop260@vauxoo.com https://github.com/moylop260
Nhomar Hernández nhomar@vauxoo.com https://github.com/nhomar
Sabirna Romero sabrina@vauxoo.com https://github.com/dsabrinarg
Yanina Aular yanina.aular@vauxoo.com https://github.com/yaniaular
+47
View File
@@ -0,0 +1,47 @@
# Odoo Individual Contributor License Agreement
## Odoo ICLA v1.0
Based on the Apache Software Foundation Individual Contributor License Agreement v2.0, with modifications
Thank you for your interest in an Odoo S.A. (the "Project Leads" ) open source project. In order to clarify the intellectual property license granted with Contributions from any person or entity, the Odoo Project Leads must have a Contributor License Agreement (the "Agreement") on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of the Project, its users, and the Odoo Project Leads; it does not change your rights to use your own Contributions for any other purpose. If you have not already done so, please complete and sign the Agreement by:
* following the electronic procedure to complete, sign and submit the ICLA at https://www.odoo.com/sign-cla
* or scanning and emailing the signed Agreement to cla-submission@odoo.com
**Please read this document carefully before signing and keep a copy for your records.**
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the Project. Except for the license granted herein to the Project Leads and recipients of software distributed by the Project Leads, You reserve all right, title, and interest in and to Your Contributions.
1. Definitions.
"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project Leads for inclusion in, or documentation of, any of the products managed or maintained by the Project Leads (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project Leads or their representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project Leads for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to the Project Leads and to recipients of software distributed by the Project Leads a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to the Project Leads and to recipients of software distributed by the Project Leads a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to the Project Leads, or that your employer has executed a separate Corporate Contributor License Agreement with the Project Leads.
5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
7. Should You wish to submit work that is not Your original creation, You may submit it to the Project Leads separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
8. You agree to notify the Project Leads of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
```
Full Name: _________________________________________________
Email: _____________________________________________________
Telephone: _________________________________________________
Mailing Address: ___________________________________________
___________________________________________
Country: ___________________________________________________
Signature: _________________________________________________
Date: ______________________________________________________
```
+11
View File
@@ -0,0 +1,11 @@
Costa Rica, 2015-02-08
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Mario Arias Badilla the.clone.master@gmail.com https://github.com/TheCloneMaster
+11
View File
@@ -0,0 +1,11 @@
Estonia, 2015-02-10
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Anton Chepurov anton.chepurov@gmail.com https://github.com/a0c
+11
View File
@@ -0,0 +1,11 @@
Belgium, 2015-02-07
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Adrien Peiffer adrien.peiffer@acsone.eu https://github.com/adrienpeiffer
+11
View File
@@ -0,0 +1,11 @@
Egypt, 2015-02-07
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Mohammad Alhashash alhashash@alhashash.net https://github.com/alhashash
+11
View File
@@ -0,0 +1,11 @@
China, 2015-02-09
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Buke Wang wangbuke@gmail.com https://github.com/buke
+11
View File
@@ -0,0 +1,11 @@
France, 2015-02-07
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Christophe Combelles ccomb@free.fr https://github.com/ccomb
+11
View File
@@ -0,0 +1,11 @@
R. Macedonia, 2015-02-08
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Kiril Vangelovski kiril@hacbee.com https://github.com/kirca
+9
View File
@@ -0,0 +1,9 @@
Germany, 2015-02-08
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Stefan Reisich nafex@gmx.net https://github.com/nafex
+11
View File
@@ -0,0 +1,11 @@
France, 2015-02-09,
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Nicolas Piganeau nicolas.piganeau@ndp-systemes.fr https://github.com/npiganeau
+11
View File
@@ -0,0 +1,11 @@
Belgium, 2015-02-06
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Olivier Dony odo@odoo.com https://github.com/odony
+154
View File
@@ -0,0 +1,154 @@
# Sign the Odoo CLA
In order for your contribution to Odoo to be accepted, you have to sign the
Odoo Contributor License Agreement (CLA). More information about this
requirement is explained in the [FAQ](#faq).
## If you are an individual
1. Read the [Individual Contributor License Agreement](icla-1.0.md)
2. Modify your current pull request, or make a new pull request on
[odoo/odoo](/odoo/odoo), adding a new file `<github-login>.md` under the
[`doc/cla/individual`](individual/) directory. If your GitHub login is
`odony`, the file would be `doc/cla/individual/odony.md`. The file should
contain:
```
<country>, <date>
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
<name> <email> https://github.com/<login>
```
Replacing the following placeholders:
* `<country>`: your country
* `<date>`: current date in the form `YYYY-MM-DD`
* `<name>`: your name
* `<email>`: your email
* `<login>`: your GitHub login
3. An Odoo R&D Team member will verify and accept your Pull Request. You can
make other pull requests, but we won't be able to merge them until your CLA
signature is merged.
## If you work for a company
1. Read the [Corporate Contributor License Agreement](icla-1.0.md)
2. Modify your current pull request, or make a new pull request on
[odoo/odoo](/odoo/odoo), adding a new file `<lowercase-company-name>.md`
under the [`doc/cla/corporate`](corporate/) directory.
If the name of the company is Odoo, the file would be
`doc/cla/corporate/odoo.md`. The file should contain:
```
<country>, <date>
<company-name> agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
<name> <email> https://github.com/<login>
List of contributors:
<name> <email> https://github.com/<login>
```
The List of contributors should list the individual contributors working
for the company.
Replacing the following placeholders:
* `<country>`: your country
* `<date>`: current date in the form `YYYY-MM-DD`
* `<name>`: your name
* `<email>`: your email
* `<login>`: your GitHub login
3. An Odoo R&D Team member will verify and accept your Pull Request. You can
make other pull requests, but we won't be able to merge them until your CLA
signature is merged.
## If you dont have a github account
If you cannot submit your signature using a pull request, you may alternatively
print the CLA, complete it, sign it, scan it and send it by email to
`cla-submission` `at` `odoo.com`. In that case someone from the Odoo team will
make the pull request on your behalf.
* Printable Odoo CCLA v1.0 https://www.odoo.com/files/legal/Odoo-CCLA-v1.0.pdf
* Printable Odoo ICLA v1.0 https://www.odoo.com/files/legal/Odoo-ICLA-v1.0.pdf
# FAQ
## Why do i need to accept a CLA ?
The goal of having a Contributor License Agreement for Odoo is to:
* clearly define the terms under which intellectual property (patches, pull
requests, etc.) have been contributed to the Odoo project
* protect the Odoo project and its users in case of legal dispute about the
origin or ownership of any part of the code
* protect the Odoo project and its users from bad actors who would contribute
and then try to withdraw their contributions or cause legal trouble, e.g. in
the form of patent lawsuits
This is done by establishing a credible, non-repudiable record that each
contributor really intended to contribute to the Odoo project under specific
terms, and they had the right to make those contributions.
The CLA is for the protection of the contributors, the project and its users.
It does not change your rights to use your own contributions for any other
purpose.
The Odoo CLA is based on the Apache Software Foundation CLA v2.0, as
can be found on the Apache website.
This CLA is not a copyright assignment, it is a pure license agreement. You
keep the full copyright for your contributions, you simply provide an
irrevocable license to the project maintainer, Odoo S.A. to use, modify and
distribute your contributions without further restrictions.
## How does it work?
Each individual contributor (making contributions only on their own behalf) is
required to sign and submit the Individual Contributor License Agreement
(ICLA) of Odoo. The agreement is executed by adding your name and
signature to the list of validated contributors inside the project source code.
If someone is unable to sign the CLA, their contributions will have to be
removed from the Odoo project.
In addition, if some or all of someone's contributions are written as part of
an employment by somebody else, the work may not belong to the contributor but
to their employer, depending on the contract terms and local laws. In that case
the employer needs to sign the Corporate Contributor License Agreement (CCLA),
including the names of all contributors allowed to make those contributions in
order for those contributions to be accepted.
The contributors should still sign the Individual CLA, in order to cover the
contributions that do not belong to the employer.
Contributors who have signed the ICLA without a CCLA from their employer should
be very careful.
The ICLA (section 4) is a legal declaration where the contributor states they
have the right to make the contributions. These contributors should only
submit contributions they are really entitled to license.
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/python
#
# Runme From the repo toplevel directory
#
import subprocess
import glob
import re
import pprint
cla_glob = "doc/cla/*/*.md"
cla = ''.join(open(f).read() for f in glob.glob(cla_glob))
cla = cla.lower()
def cla_signed(email):
if re.match('.*(odoo|openerp|tinyerp).com$',email):
return True
if cla.find(email) != -1:
return True
return False
def blamestat(ext='py'):
r = {}
ok = 0
okl = []
ko = 0
kol = []
p = subprocess.Popen("git ls-tree -r -z --name-only HEAD | grep -z '.%s$' | xargs -0 -n1 git blame --line-porcelain HEAD |grep '^author-mail ' |sort |uniq -c|sort -nr" % ext, shell=True, stdout = subprocess.PIPE)
for i in p.stdout.read().split('\n'):
mo = re.search('(\d+) author-mail <([^ @<]+@[^ @<]+)>',i)
if mo:
lines = int(mo.group(1))
email = mo.group(2)
if cla_signed(email):
ok += lines
okl.append(i)
else:
ko += lines
kol.append(i)
print '-'*60
print 'Stats for ',ext
print '-'*60
print "\nCLA SIGNED %s/%s (%.0f%%)" % (ok, ok+ko, ok*100.0/(ok+ko))
for i in okl:
print i
print "\nCLA MISSING %s/%s (%.0f%%)\n" % (ko, ok+ko, ko*100.0/(ok+ko))
for i in kol:
print i
print
print
blamestat('md')
blamestat('rst')
blamestat('py')
blamestat('js')
blamestat('xml')
blamestat('csv')
+3 -3
View File
@@ -498,7 +498,7 @@ Two decorators can expose a new-style method to the old API:
empty. Its "old API" signature is ``cr, uid, *arguments, context``::
@api.model
def some_method(foo):
def some_method(self, a_value):
pass
# can be called as
old_style_model.some_method(cr, uid, a_value, context=context)
@@ -508,7 +508,7 @@ Two decorators can expose a new-style method to the old API:
"old API" signature is ``cr, uid, ids, *arguments, context``::
@api.multi
def some_method(foo):
def some_method(self, a_value):
pass
# can be called as
old_style_model.some_method(cr, uid, [id1, id2], a_value, context=context)
@@ -525,7 +525,7 @@ return lists of ids, there is also a decorator managing this:
>>> @api.multi
... @api.returns('self')
... def some_method():
... def some_method(self):
... return self
>>> new_style_model = env['a.model'].browse(1, 2, 3)
>>> new_style_model.some_method()
+4 -3
View File
@@ -217,10 +217,11 @@ class res_partner_bank(osv.osv):
return {'value': result}
def onchange_partner_id(self, cr, uid, id, partner_id, context=None):
def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
result = {}
if partner_id:
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
if partner_id is not False:
# be careful: partner_id may be a NewId
part = self.pool['res.partner'].browse(cr, uid, [partner_id], context=context)
result['owner_name'] = part.name
result['street'] = part.street or False
result['city'] = part.city or False
+1 -1
View File
@@ -119,7 +119,7 @@
<field name="city" class="oe_inline" placeholder="City"/>
</div>
<field name="state_id" placeholder="State" options='{"no_open": True}'/>
<field name="country_id" placeholder="Country" options='{"no_open": True}'/>
<field name="country_id" placeholder="Country" options='{"no_open": True, "no_create": True}'/>
</div>
</group>
<group name="bank" string="Information About the Bank">

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