[MERGE] forward port of branch 8.0 up to 92c7874
This commit is contained in:
@@ -383,12 +383,17 @@ class account_bank_statement(osv.osv):
|
||||
return {'value': res}
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
for item in self.browse(cr, uid, ids, context=context):
|
||||
if item.state != 'draft':
|
||||
raise osv.except_osv(
|
||||
_('Invalid Action!'),
|
||||
_('In order to delete a bank statement, you must first cancel it to delete related journal items.')
|
||||
)
|
||||
# Explicitly unlink bank statement lines
|
||||
# so it will check that the related journal entries have
|
||||
# been deleted first
|
||||
statement_line_obj.unlink(cr, uid, [line.id for line in item.line_ids], context=context)
|
||||
return super(account_bank_statement, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
def button_journal_entries(self, cr, uid, ids, context=None):
|
||||
@@ -809,7 +814,7 @@ class account_bank_statement_line(osv.osv):
|
||||
'partner_id': fields.many2one('res.partner', 'Partner'),
|
||||
'bank_account_id': fields.many2one('res.partner.bank','Bank Account'),
|
||||
'account_id': fields.many2one('account.account', 'Account', help="This technical field can be used at the statement line creation/import time in order to avoid the reconciliation process on it later on. The statement line will simply create a counterpart on this account"),
|
||||
'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='cascade'),
|
||||
'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='restrict'),
|
||||
'journal_id': fields.related('statement_id', 'journal_id', type='many2one', relation='account.journal', string='Journal', store=True, readonly=True),
|
||||
'partner_name': fields.char('Partner Name', help="This field is used to record the third party name when importing bank statement in electronic format, when the partner doesn't exist yet in the database (or cannot be found)."),
|
||||
'ref': fields.char('Reference'),
|
||||
|
||||
@@ -318,16 +318,26 @@ class account_invoice(models.Model):
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type=False, toolbar=False, submenu=False):
|
||||
context = self._context
|
||||
|
||||
def get_view_id(xid, name):
|
||||
try:
|
||||
return self.env['ir.model.data'].xmlid_to_res_id('account.' + xid, raise_if_not_found=True)
|
||||
except ValueError:
|
||||
try:
|
||||
return self.env['ir.ui.view'].search([('name', '=', name)], limit=1).id
|
||||
except Exception:
|
||||
return False # view not found
|
||||
|
||||
if context.get('active_model') == 'res.partner' and context.get('active_ids'):
|
||||
partner = self.env['res.partner'].browse(context['active_ids'])[0]
|
||||
if not view_type:
|
||||
view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.tree')]).id
|
||||
view_id = get_view_id('invoice_tree', 'account.invoice.tree')
|
||||
view_type = 'tree'
|
||||
elif view_type == 'form':
|
||||
if partner.supplier and not partner.customer:
|
||||
view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.supplier.form')]).id
|
||||
view_id = get_view_id('invoice_supplier_form', 'account.invoice.supplier.form')
|
||||
elif partner.customer and not partner.supplier:
|
||||
view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.form')]).id
|
||||
view_id = get_view_id('invoice_form', 'account.invoice.form')
|
||||
|
||||
res = super(account_invoice, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
|
||||
|
||||
|
||||
@@ -47,6 +47,16 @@ class account_fiscal_position(osv.osv):
|
||||
'active': True,
|
||||
}
|
||||
|
||||
def _check_country(self, cr, uid, ids, context=None):
|
||||
obj = self.browse(cr, uid, ids[0], context=context)
|
||||
if obj.country_id and obj.country_group_id:
|
||||
return False
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_country, 'You can not select a country and a group of countries', ['country_id', 'country_group_id']),
|
||||
]
|
||||
|
||||
@api.v7
|
||||
def map_tax(self, cr, uid, fposition_id, taxes, context=None):
|
||||
if not taxes:
|
||||
@@ -69,12 +79,13 @@ class account_fiscal_position(osv.osv):
|
||||
def map_tax(self, taxes):
|
||||
result = self.env['account.tax'].browse()
|
||||
for tax in taxes:
|
||||
tax_count = 0
|
||||
for t in self.tax_ids:
|
||||
if t.tax_src_id == tax:
|
||||
tax_count += 1
|
||||
if t.tax_dest_id:
|
||||
result |= t.tax_dest_id
|
||||
break
|
||||
else:
|
||||
if not tax_count:
|
||||
result |= tax
|
||||
return result
|
||||
|
||||
@@ -115,10 +126,17 @@ class account_fiscal_position(osv.osv):
|
||||
domain = [
|
||||
('auto_apply', '=', True),
|
||||
'|', ('vat_required', '=', False), ('vat_required', '=', partner.vat_subjected),
|
||||
'|', ('country_id', '=', None), ('country_id', '=', delivery.country_id.id),
|
||||
'|', ('country_group_id', '=', None), ('country_group_id.country_ids', '=', delivery.country_id.id)
|
||||
]
|
||||
fiscal_position_ids = self.search(cr, uid, domain, context=context)
|
||||
|
||||
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_id', '=', None), ('country_group_id', '=', None)], context=context, limit=1)
|
||||
if fiscal_position_ids:
|
||||
return fiscal_position_ids[0]
|
||||
return False
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<field name="auto_apply"/>
|
||||
<field name="sequence"/>
|
||||
<field name="vat_required" attrs="{'readonly': [('auto_apply', '=', False)]}"/>
|
||||
<field name="country_id" attrs="{'readonly': ['|', ('country_group_id','!=',False), ('auto_apply', '=', False)]}" />
|
||||
<field name="country_group_id" attrs="{'readonly': ['|', ('country_id','!=',False), ('auto_apply', '=', False)]}"/>
|
||||
<field name="country_id"/>
|
||||
<field name="country_group_id"/>
|
||||
</group>
|
||||
<separator string="Taxes Mapping"/>
|
||||
<field name="tax_ids" widget="one2many_list">
|
||||
|
||||
@@ -33,10 +33,6 @@ class partner_balance(report_sxw.rml_parse, common_report_header):
|
||||
self.account_ids = []
|
||||
self.localcontext.update( {
|
||||
'time': time,
|
||||
'lines': self.lines,
|
||||
'sum_debit': self._sum_debit,
|
||||
'sum_credit': self._sum_credit,
|
||||
'sum_litige': self._sum_litige,
|
||||
'get_fiscalyear': self._get_fiscalyear,
|
||||
'get_journal': self._get_journal,
|
||||
'get_filter': self._get_filter,
|
||||
@@ -70,7 +66,20 @@ class partner_balance(report_sxw.rml_parse, common_report_header):
|
||||
"WHERE a.type IN %s " \
|
||||
"AND a.active", (self.ACCOUNT_TYPE,))
|
||||
self.account_ids = [a for (a,) in self.cr.fetchall()]
|
||||
return super(partner_balance, self).set_context(objects, data, ids, report_type=report_type)
|
||||
res = super(partner_balance, self).set_context(objects, data, ids, report_type=report_type)
|
||||
lines = self.lines()
|
||||
sum_debit = sum_credit = sum_litige = 0
|
||||
for line in filter(lambda x: x['type'] == 3, lines):
|
||||
sum_debit += line['debit'] or 0
|
||||
sum_credit += line['credit'] or 0
|
||||
sum_litige += line['enlitige'] or 0
|
||||
self.localcontext.update({
|
||||
'lines': lambda: lines,
|
||||
'sum_debit': lambda: sum_debit,
|
||||
'sum_credit': lambda: sum_credit,
|
||||
'sum_litige': lambda: sum_litige,
|
||||
})
|
||||
return res
|
||||
|
||||
def lines(self):
|
||||
move_state = ['draft','posted']
|
||||
@@ -236,62 +245,6 @@ class partner_balance(report_sxw.rml_parse, common_report_header):
|
||||
i = i + 1
|
||||
return completearray
|
||||
|
||||
def _sum_debit(self):
|
||||
move_state = ['draft','posted']
|
||||
if self.target_move == 'posted':
|
||||
move_state = ['posted']
|
||||
|
||||
if not self.ids:
|
||||
return 0.0
|
||||
self.cr.execute(
|
||||
"SELECT sum(debit) " \
|
||||
"FROM account_move_line AS l " \
|
||||
"JOIN account_move am ON (am.id = l.move_id)" \
|
||||
"WHERE l.account_id IN %s" \
|
||||
"AND am.state IN %s" \
|
||||
"AND " + self.query + "",
|
||||
(tuple(self.account_ids), tuple(move_state)))
|
||||
temp_res = float(self.cr.fetchone()[0] or 0.0)
|
||||
return temp_res
|
||||
|
||||
def _sum_credit(self):
|
||||
move_state = ['draft','posted']
|
||||
if self.target_move == 'posted':
|
||||
move_state = ['posted']
|
||||
|
||||
if not self.ids:
|
||||
return 0.0
|
||||
self.cr.execute(
|
||||
"SELECT sum(credit) " \
|
||||
"FROM account_move_line AS l " \
|
||||
"JOIN account_move am ON (am.id = l.move_id)" \
|
||||
"WHERE l.account_id IN %s" \
|
||||
"AND am.state IN %s" \
|
||||
"AND " + self.query + "",
|
||||
(tuple(self.account_ids), tuple(move_state)))
|
||||
temp_res = float(self.cr.fetchone()[0] or 0.0)
|
||||
return temp_res
|
||||
|
||||
def _sum_litige(self):
|
||||
#gives the total of move lines with blocked boolean set to TRUE for the report selection
|
||||
move_state = ['draft','posted']
|
||||
if self.target_move == 'posted':
|
||||
move_state = ['posted']
|
||||
|
||||
if not self.ids:
|
||||
return 0.0
|
||||
self.cr.execute(
|
||||
"SELECT sum(debit-credit) " \
|
||||
"FROM account_move_line AS l " \
|
||||
"JOIN account_move am ON (am.id = l.move_id)" \
|
||||
"WHERE l.account_id IN %s" \
|
||||
"AND am.state IN %s" \
|
||||
"AND " + self.query + " " \
|
||||
"AND l.blocked=TRUE ",
|
||||
(tuple(self.account_ids), tuple(move_state), ))
|
||||
temp_res = float(self.cr.fetchone()[0] or 0.0)
|
||||
return temp_res
|
||||
|
||||
def _get_partners(self):
|
||||
|
||||
if self.result_selection == 'customer':
|
||||
|
||||
@@ -392,7 +392,7 @@ class account_invoice(osv.osv):
|
||||
if inv.type in ('in_invoice', 'in_refund'):
|
||||
ref = inv.reference
|
||||
else:
|
||||
ref = self._convert_ref(inv.number)
|
||||
ref = inv.number
|
||||
obj_move_line = acct_ins_obj.browse(cr, uid, il['analytics_id'], context=context)
|
||||
ctx = context.copy()
|
||||
ctx.update({'date': inv.date_invoice})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@charset "utf-8"
|
||||
|
||||
@mixin radius($radius: 5px)
|
||||
-moz-border-radius: $radius
|
||||
-webkit-border-radius: $radius
|
||||
|
||||
@@ -58,4 +58,4 @@
|
||||
// singleton
|
||||
bus.bus = new bus.Bus();
|
||||
return bus;
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
name="hr_timesheet_invoice.report_analyticprofit"
|
||||
file="hr_timesheet_invoice.report_analyticprofit"
|
||||
report_type="qweb-pdf"
|
||||
menu="False"
|
||||
string="Timesheet Profit"
|
||||
/>
|
||||
</data>
|
||||
|
||||
@@ -39,13 +39,15 @@ class account_analytic_profit(report_sxw.rml_parse):
|
||||
return user_obj.browse(self.cr, self.uid, ids)
|
||||
|
||||
def _journal_ids(self, form, user_id):
|
||||
if isinstance(user_id, (int, long)):
|
||||
user_id = [user_id]
|
||||
line_obj = self.pool['account.analytic.line']
|
||||
journal_obj = self.pool['account.analytic.journal']
|
||||
line_ids=line_obj.search(self.cr, self.uid, [
|
||||
('date', '>=', form['date_from']),
|
||||
('date', '<=', form['date_to']),
|
||||
('journal_id', 'in', form['journal_ids'][0][2]),
|
||||
('user_id', '=', user_id),
|
||||
('user_id', 'in', user_id),
|
||||
])
|
||||
ids=list(set([b.journal_id.id for b in line_obj.browse(self.cr, self.uid, line_ids)]))
|
||||
return journal_obj.browse(self.cr, self.uid, ids)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Actif</h3>
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -538,6 +539,154 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Passif</h3>
|
||||
<table class="table table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>CAPITAUX PROPRES</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Capital [dont versé...]</td>
|
||||
<td><span t-esc="bpvar1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Primes d'émission, de fusion, d'apport</td>
|
||||
<td><span t-esc="bpvar2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Écarts de réévaluation</td>
|
||||
<td><span t-esc="bpvar3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Écart d'équivalence</td>
|
||||
<td><span t-esc="bpvar4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>RÉSERVES</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Réserve légale</td>
|
||||
<td><span t-esc="bpvar5" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Réserves statutaires ou contractuelles</td>
|
||||
<td><span t-esc="bpvar6" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Réserves réglementées</td><td><span t-esc="bpvar7" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td></tr>
|
||||
<tr>
|
||||
<td>Autres réserves</td>
|
||||
<td><span t-esc="bpvar8" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Report à nouveau</td>
|
||||
<td><span t-esc="bpvar9" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>RÉSULTAT DE L'EXERCICE [bénéfice ou perte]</strong></td>
|
||||
<td><span t-esc="bpvar10" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subventions d'investissement</td>
|
||||
<td><span t-esc="bpvar11" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisions réglementées</td>
|
||||
<td><span t-esc="bpvar12" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL I</strong></td>
|
||||
<td><span t-esc="pt1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>PROVISIONS</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisions pour risques</td>
|
||||
<td><span t-esc="bpvar13" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisions pour charges</td>
|
||||
<td><span t-esc="bpvar14" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL II</strong></td>
|
||||
<td><span t-esc="pt2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>DETTES</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emprunts obligataires convertibles</td>
|
||||
<td><span t-esc="bpvar15" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres emprunts obligataires</td>
|
||||
<td><span t-esc="bpvar16" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emprunts et dettes auprès des établissements de crédit</td>
|
||||
<td><span t-esc="bpvar17" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emprunts et dettes financières diverses</td>
|
||||
<td><span t-esc="bpvar18" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Avances et acomptes reçus sur commandes en cours</td>
|
||||
<td><span t-esc="bpvar19" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dettes fournisseurs et comptes rattachés </td>
|
||||
<td><span t-esc="bpvar20" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dettes fiscales et sociales</td>
|
||||
<td><span t-esc="bpvar21" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dettes sur immobilisations et comptes rattachés</td>
|
||||
<td><span t-esc="bpvar22" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autres dettes</td>
|
||||
<td><span t-esc="bpvar23" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Instruments de trésorerie</td>
|
||||
<td><span t-esc="bpvar24" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Produits constatés d'avance</td>
|
||||
<td><span t-esc="bpvar25" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right"><strong>TOTAL III</strong></td>
|
||||
<td><span t-esc="pt3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Écarts de conversion passif <font face="Times-Roman">( IV )</font></td>
|
||||
<td><span t-esc="bpvar26" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>TOTAL GÉNÉRAL (I + II + III + IV)</strong></td>
|
||||
<td><span t-esc="passif" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>&nbsp;</td>
|
||||
<td>&nbsp;</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>ACTIF - PASSIF</strong></td>
|
||||
<td><span t-esc="round(actif-passif,2)" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
+15
-2
@@ -931,9 +931,19 @@ class mrp_production(osv.osv):
|
||||
lot_id = False
|
||||
if wiz:
|
||||
lot_id = wiz.lot_id.id
|
||||
new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty_uom),
|
||||
qty = min(subproduct_factor * production_qty_uom, produce_product.product_qty) #Needed when producing more than maximum quantity
|
||||
new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], qty,
|
||||
location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
|
||||
stock_mov_obj.write(cr, uid, new_moves, {'production_id': production_id}, context=context)
|
||||
remaining_qty = subproduct_factor * production_qty_uom - qty
|
||||
if remaining_qty: # In case you need to make more than planned
|
||||
#consumed more in wizard than previously planned
|
||||
extra_move_id = stock_mov_obj.copy(cr, uid, produce_product.id, default={'state': 'confirmed',
|
||||
'product_uom_qty': remaining_qty,
|
||||
'production_id': production_id}, context=context)
|
||||
if extra_move_id:
|
||||
stock_mov_obj.action_done(cr, uid, [extra_move_id], context=context)
|
||||
|
||||
if produce_product.product_id.id == production.product_id.id:
|
||||
main_production_move = produce_product.id
|
||||
|
||||
@@ -952,13 +962,16 @@ class mrp_production(osv.osv):
|
||||
if consume['product_id'] != raw_material_line.product_id.id:
|
||||
continue
|
||||
consumed_qty = min(remaining_qty, raw_material_line.product_qty)
|
||||
stock_mov_obj.action_consume(cr, uid, [raw_material_line.id], consumed_qty, raw_material_line.location_id.id, restrict_lot_id=consume['lot_id'], consumed_for=main_production_move, context=context)
|
||||
stock_mov_obj.action_consume(cr, uid, [raw_material_line.id], consumed_qty, raw_material_line.location_id.id,
|
||||
restrict_lot_id=consume['lot_id'], consumed_for=main_production_move, context=context)
|
||||
remaining_qty -= consumed_qty
|
||||
if remaining_qty:
|
||||
#consumed more in wizard than previously planned
|
||||
product = self.pool.get('product.product').browse(cr, uid, consume['product_id'], context=context)
|
||||
extra_move_id = self._make_consume_line_from_data(cr, uid, production, product, product.uom_id.id, remaining_qty, False, 0, context=context)
|
||||
if extra_move_id:
|
||||
if consume['lot_id']:
|
||||
stock_mov_obj.write(cr, uid, [extra_move_id], {'restrict_lot_id': consume['lot_id']}, context=context)
|
||||
stock_mov_obj.action_done(cr, uid, [extra_move_id], context=context)
|
||||
|
||||
self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context)
|
||||
|
||||
@@ -315,7 +315,12 @@ class product_pricelist(osv.osv):
|
||||
price = price * (1.0+(rule.price_discount or 0.0))
|
||||
if rule.price_round:
|
||||
price = tools.float_round(price, precision_rounding=rule.price_round)
|
||||
price += (rule.price_surcharge or 0.0)
|
||||
if context.get('uom'):
|
||||
# compute price_surcharge based on reference uom
|
||||
factor = product_uom_obj.browse(cr, uid, context.get('uom'), context=context).factor
|
||||
else:
|
||||
factor = 1.0
|
||||
price += (rule.price_surcharge or 0.0) / factor
|
||||
if rule.price_min_margin:
|
||||
price = max(price, price_limit+rule.price_min_margin)
|
||||
if rule.price_max_margin:
|
||||
|
||||
@@ -557,7 +557,7 @@ class product_template(osv.osv):
|
||||
|
||||
'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the product without removing it."),
|
||||
'color': fields.integer('Color Index'),
|
||||
'is_product_variant': fields.function( _is_product_variant, type='boolean', string='Only one product variant'),
|
||||
'is_product_variant': fields.function( _is_product_variant, type='boolean', string='Is product variant'),
|
||||
|
||||
'attribute_line_ids': fields.one2many('product.attribute.line', 'product_tmpl_id', 'Product Attributes'),
|
||||
'product_variant_ids': fields.one2many('product.product', 'product_tmpl_id', 'Products', required=True),
|
||||
@@ -918,6 +918,7 @@ class product_product(osv.osv):
|
||||
'product.product': (lambda self, cr, uid, ids, c=None: ids, [], 10),
|
||||
}, select=True),
|
||||
'attribute_value_ids': fields.many2many('product.attribute.value', id1='prod_id', id2='att_id', string='Attributes', readonly=True, ondelete='restrict'),
|
||||
'is_product_variant': fields.function( _is_product_variant_impl, type='boolean', string='Is product variant'),
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
'image_variant': fields.binary("Variant Image",
|
||||
@@ -1110,6 +1111,34 @@ class product_product(osv.osv):
|
||||
def need_procurement(self, cr, uid, ids, context=None):
|
||||
return False
|
||||
|
||||
def _compute_uos_qty(self, cr, uid, ids, uom, qty, uos, context=None):
|
||||
'''
|
||||
Computes product's invoicing quantity in UoS from quantity in UoM.
|
||||
Takes into account the
|
||||
:param uom: Source unit
|
||||
:param qty: Source quantity
|
||||
:param uos: Target UoS unit.
|
||||
'''
|
||||
if not uom or not qty or not uos:
|
||||
return qty
|
||||
uom_obj = self.pool['product.uom']
|
||||
product_id = ids[0] if isinstance(ids, (list, tuple)) else ids
|
||||
product = self.browse(cr, uid, product_id, context=context)
|
||||
if isinstance(uos, (int, long)):
|
||||
uos = uom_obj.browse(cr, uid, uos, context=context)
|
||||
if isinstance(uom, (int, long)):
|
||||
uom = uom_obj.browse(cr, uid, uom, context=context)
|
||||
if product.uos_id: # Product has UoS defined
|
||||
# We cannot convert directly between units even if the units are of the same category
|
||||
# as we need to apply the conversion coefficient which is valid only between quantities
|
||||
# in product's default UoM/UoS
|
||||
qty_default_uom = uom_obj._compute_qty_obj(cr, uid, uom, qty, product.uom_id) # qty in product's default UoM
|
||||
qty_default_uos = qty_default_uom * product.uos_coeff
|
||||
return uom_obj._compute_qty_obj(cr, uid, product.uos_id, qty_default_uos, uos)
|
||||
else:
|
||||
return uom_obj._compute_qty_obj(cr, uid, uom, qty, uos)
|
||||
|
||||
|
||||
|
||||
class product_packaging(osv.osv):
|
||||
_name = "product.packaging"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from . import test_uom
|
||||
from . import test_uom, test_pricelist
|
||||
|
||||
fast_suite = [
|
||||
test_uom,
|
||||
test_pricelist
|
||||
]
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
from openerp.tests.common import TransactionCase
|
||||
|
||||
class TestPricelist(TransactionCase):
|
||||
"""Tests for unit of measure conversion"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestPricelist, self).setUp()
|
||||
cr, uid, context = self.cr, self.uid, {}
|
||||
self.ir_model_data = self.registry('ir.model.data')
|
||||
self.product_product = self.registry('product.product')
|
||||
self.product_pricelist = self.registry('product.pricelist')
|
||||
self.uom = self.registry('product.uom')
|
||||
|
||||
self.usb_adapter_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_48')[1]
|
||||
self.datacard_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_46')[1]
|
||||
self.unit_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_uom_unit')[1]
|
||||
self.dozen_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_uom_dozen')[1]
|
||||
|
||||
self.public_pricelist_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'list0')[1]
|
||||
self.sale_pricelist_id = self.product_pricelist.create(cr, uid, {
|
||||
'name': 'Sale pricelist',
|
||||
'type': 'sale',
|
||||
'version_id': [(0, 0, {
|
||||
'name': 'v1.0',
|
||||
'items_id': [(0, 0, {
|
||||
'name': 'Discount 10%',
|
||||
'base': 1, # based on public price
|
||||
'price_discount': -0.1,
|
||||
'product_id': self.usb_adapter_id
|
||||
}), (0, 0, {
|
||||
'name': 'Discount -0.5',
|
||||
'base': 1, # based on public price
|
||||
'price_surcharge': -0.5,
|
||||
'product_id': self.datacard_id
|
||||
})]
|
||||
})]
|
||||
}, context=context)
|
||||
|
||||
def test_10_discount(self):
|
||||
# Make sure the price using a pricelist is the same than without after
|
||||
# applying the computation manually
|
||||
cr, uid, context = self.cr, self.uid, {}
|
||||
|
||||
public_context = dict(context, pricelist=self.public_pricelist_id)
|
||||
pricelist_context = dict(context, pricelist=self.sale_pricelist_id)
|
||||
|
||||
usb_adapter_without_pricelist = self.product_product.browse(cr, uid, self.usb_adapter_id, context=public_context)
|
||||
usb_adapter_with_pricelist = self.product_product.browse(cr, uid, self.usb_adapter_id, context=pricelist_context)
|
||||
self.assertEqual(usb_adapter_with_pricelist.price, usb_adapter_without_pricelist.price*0.9)
|
||||
|
||||
datacard_without_pricelist = self.product_product.browse(cr, uid, self.datacard_id, context=public_context)
|
||||
datacard_with_pricelist = self.product_product.browse(cr, uid, self.datacard_id, context=pricelist_context)
|
||||
self.assertEqual(datacard_with_pricelist.price, datacard_without_pricelist.price-0.5)
|
||||
|
||||
# Make sure that changing the unit of measure does not break the unit
|
||||
# price (after converting)
|
||||
unit_context = dict(context,
|
||||
pricelist=self.sale_pricelist_id,
|
||||
uom=self.unit_id)
|
||||
dozen_context = dict(context,
|
||||
pricelist=self.sale_pricelist_id,
|
||||
uom=self.dozen_id)
|
||||
|
||||
usb_adapter_unit = self.product_product.browse(cr, uid, self.usb_adapter_id, context=unit_context)
|
||||
usb_adapter_dozen = self.product_product.browse(cr, uid, self.usb_adapter_id, context=dozen_context)
|
||||
self.assertAlmostEqual(usb_adapter_unit.price*12, usb_adapter_dozen.price)
|
||||
|
||||
datacard_unit = self.product_product.browse(cr, uid, self.datacard_id, context=unit_context)
|
||||
datacard_dozen = self.product_product.browse(cr, uid, self.datacard_id, context=dozen_context)
|
||||
self.assertAlmostEqual(datacard_unit.price*12, datacard_dozen.price)
|
||||
@@ -43,6 +43,7 @@ class sale_order_line(osv.osv):
|
||||
fiscal_position=False, flag=False, context=None):
|
||||
|
||||
def get_real_price(res_dict, product_id, qty, uom, pricelist):
|
||||
"""Retrieve the price before applying the pricelist"""
|
||||
item_obj = self.pool.get('product.pricelist.item')
|
||||
price_type_obj = self.pool.get('product.price.type')
|
||||
product_obj = self.pool.get('product.product')
|
||||
@@ -58,9 +59,8 @@ class sale_order_line(osv.osv):
|
||||
|
||||
factor = 1.0
|
||||
if uom and uom != product.uom_id.id:
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
uom_data = product_uom_obj.browse(cr, uid, product.uom_id.id)
|
||||
factor = uom_data.factor
|
||||
# the unit price is in a different uom
|
||||
factor = self.pool['product.uom']._compute_qty(cr, uid, uom, 1.0, product.uom_id.id)
|
||||
return product_read[field_name] * factor
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class sale_order_line(osv.osv):
|
||||
price=result['price_unit']
|
||||
else:
|
||||
return res
|
||||
|
||||
uom = result.get('product_uom', uom)
|
||||
product = product_obj.browse(cr, uid, product, context)
|
||||
pricelist_context = dict(context, uom=uom, date=date_order)
|
||||
list_price = pricelist_obj.price_rule_get(cr, uid, [pricelist],
|
||||
|
||||
@@ -1,125 +1,129 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="report_purchaseorder_document">
|
||||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<div class="oe_structure"/>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
Shipping address :<br/>
|
||||
<div t-if="o.dest_address_id">
|
||||
<div t-field="o.dest_address_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
|
||||
<div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-5 col-xs-offset-1">
|
||||
<div t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 t-if="o.state != 'draft'">Purchase Order Confirmation N°<span t-field="o.name"/></h2>
|
||||
<h2 t-if="o.state == 'draft'">Request for Quotation N°<span t-field="o.name"/></h2>
|
||||
|
||||
<div class="row mt32 mb32">
|
||||
<div t-if="o.name" class="col-xs-3">
|
||||
<strong>Our Order Reference:</strong>
|
||||
<p t-field="o.name"/>
|
||||
</div>
|
||||
<div t-if="o.partner_ref" class="col-xs-3">
|
||||
<strong>Your Order Reference</strong>
|
||||
<p t-field="o.partner_ref"/>
|
||||
</div>
|
||||
<div t-if="o.date_order" class="col-xs-3">
|
||||
<strong>Order Date:</strong>
|
||||
<p t-field="o.date_order"/>
|
||||
</div>
|
||||
<div t-if="o.validator" class="col-xs-3">
|
||||
<strong>Validated By:</strong>
|
||||
<p t-field="o.validator"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>Description</strong></th>
|
||||
<th><strong>Taxes</strong></th>
|
||||
<th class="text-center"><strong>Date Req.</strong></th>
|
||||
<th class="text-right"><strong>Qty</strong></th>
|
||||
<th class="text-right"><strong>Unit Price</strong></th>
|
||||
<th class="text-right"><strong>Net Price</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.order_line" t-as="line">
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-esc="', '.join(map(lambda x: x.name, line.taxes_id))"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-field="line.date_planned"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.product_qty"/>
|
||||
<span t-field="line.product_uom.name" groups="product.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_unit"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_subtotal"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-4 pull-right">
|
||||
<table class="table table-condensed">
|
||||
<tr class="border-black">
|
||||
<td><strong>Total Without Taxes</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_untaxed"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Taxes</td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_tax"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_total"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p t-field="o.notes"/>
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="report_purchaseorder">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<div class="oe_structure"/>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
Shipping address :<br/>
|
||||
<div t-if="o.dest_address_id">
|
||||
<div t-field="o.dest_address_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
|
||||
<div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-5 col-xs-offset-1">
|
||||
<div t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 t-if="o.state != 'draft'">Purchase Order Confirmation N°<span t-field="o.name"/></h2>
|
||||
<h2 t-if="o.state == 'draft'">Request for Quotation N°<span t-field="o.name"/></h2>
|
||||
|
||||
<div class="row mt32 mb32">
|
||||
<div t-if="o.name" class="col-xs-3">
|
||||
<strong>Our Order Reference:</strong>
|
||||
<p t-field="o.name"/>
|
||||
</div>
|
||||
<div t-if="o.partner_ref" class="col-xs-3">
|
||||
<strong>Your Order Reference</strong>
|
||||
<p t-field="o.partner_ref"/>
|
||||
</div>
|
||||
<div t-if="o.date_order" class="col-xs-3">
|
||||
<strong>Order Date:</strong>
|
||||
<p t-field="o.date_order"/>
|
||||
</div>
|
||||
<div t-if="o.validator" class="col-xs-3">
|
||||
<strong>Validated By:</strong>
|
||||
<p t-field="o.validator"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>Description</strong></th>
|
||||
<th><strong>Taxes</strong></th>
|
||||
<th class="text-center"><strong>Date Req.</strong></th>
|
||||
<th class="text-right"><strong>Qty</strong></th>
|
||||
<th class="text-right"><strong>Unit Price</strong></th>
|
||||
<th class="text-right"><strong>Net Price</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.order_line" t-as="line">
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-esc="', '.join(map(lambda x: x.name, line.taxes_id))"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-field="line.date_planned"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.product_qty"/>
|
||||
<span t-field="line.product_uom.name" groups="product.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_unit"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_subtotal"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-4 pull-right">
|
||||
<table class="table table-condensed">
|
||||
<tr class="border-black">
|
||||
<td><strong>Total Without Taxes</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_untaxed"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Taxes</td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_tax"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_total"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p t-field="o.notes"/>
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="doc_ids" t-as="doc_id">
|
||||
<t t-raw="translate_doc(doc_id, doc_model, 'partner_id.lang', 'purchase.report_purchaseorder_document')"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@@ -1,67 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="report_purchasequotation_document">
|
||||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<div class="oe_structure"/>
|
||||
|
||||
<div class="row mt32 mb32">
|
||||
<div class="col-xs-6">
|
||||
Shipping address :<br/>
|
||||
<div t-if="o.dest_address_id">
|
||||
<div t-field="o.dest_address_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
<div t-if="o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-5 col-xs-offset-1">
|
||||
<div t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Request for Quotation <span t-field="o.name"/></h2>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>Description</strong></th>
|
||||
<th class="text-center"><strong>Expected Date</strong></th>
|
||||
<th class="text-right"><strong>Qty</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.order_line" t-as="order_line">
|
||||
<td>
|
||||
<span t-field="order_line.name"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-field="order_line.date_planned"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="order_line.product_qty"/>
|
||||
<span t-field="order_line.product_uom" groups="product.group_uom"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p t-field="o.notes"/>
|
||||
<span>Regards,</span>
|
||||
<span t-field="user.signature"/>
|
||||
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="report_purchasequotation">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<div class="oe_structure"/>
|
||||
|
||||
<div class="row mt32 mb32">
|
||||
<div class="col-xs-6">
|
||||
Shipping address :<br/>
|
||||
<div t-if="o.dest_address_id">
|
||||
<div t-field="o.dest_address_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
<div t-if="o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-5 col-xs-offset-1">
|
||||
<div t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Request for Quotation <span t-field="o.name"/></h2>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>Description</strong></th>
|
||||
<th class="text-center"><strong>Expected Date</strong></th>
|
||||
<th class="text-right"><strong>Qty</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.order_line" t-as="order_line">
|
||||
<td>
|
||||
<span t-field="order_line.name"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-field="order_line.date_planned"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="order_line.product_qty"/>
|
||||
<span t-field="order_line.product_uom" groups="product.group_uom"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p t-field="o.notes"/>
|
||||
<span>Regards,</span>
|
||||
<span t-field="user.signature"/>
|
||||
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="doc_ids" t-as="doc_id">
|
||||
<t t-raw="translate_doc(doc_id, doc_model, 'partner_id.lang', 'purchase.report_purchasequotation_document')"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@@ -101,7 +101,8 @@ class sale_order_line_make_invoice(osv.osv_memory):
|
||||
flag = False
|
||||
break
|
||||
if flag:
|
||||
workflow.trg_validate(uid, 'sale.order', order.id, 'manual_invoice', cr)
|
||||
line.order_id.write({'state': 'progress'})
|
||||
workflow.trg_validate(uid, 'sale.order', order.id, 'all_lines', cr)
|
||||
|
||||
if not invoices:
|
||||
raise osv.except_osv(_('Warning!'), _('Invoice cannot be created for this Sales Order Line due to one of the following reasons:\n1.The state of this sales order line is either "draft" or "cancel"!\n2.The Sales Order Line is Invoiced!'))
|
||||
|
||||
@@ -36,7 +36,11 @@ class sale_order_line(osv.osv):
|
||||
frm_cur = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
|
||||
to_cur = self.pool.get('product.pricelist').browse(cr, uid, [pricelist])[0].currency_id.id
|
||||
if product:
|
||||
purchase_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
|
||||
product = self.pool['product.product'].browse(cr, uid, product, context=context)
|
||||
purchase_price = product.standard_price
|
||||
to_uom = res.get('product_uom', uom)
|
||||
if to_uom != product.uom_id.id:
|
||||
purchase_price = self.pool['product.uom']._compute_price(cr, uid, product.uom_id.id, purchase_price, to_uom)
|
||||
ctx = context.copy()
|
||||
ctx['date'] = date_order
|
||||
price = self.pool.get('res.currency').compute(cr, uid, frm_cur, to_cur, purchase_price, round=False, context=ctx)
|
||||
|
||||
@@ -1267,7 +1267,7 @@ class stock_picking(osv.osv):
|
||||
'product_id': product.id,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': remaining_qty,
|
||||
'name': _('Extra Move: ') + op.product_id.name,
|
||||
'name': _('Extra Move: ') + product.name,
|
||||
'state': 'draft',
|
||||
}
|
||||
return res
|
||||
|
||||
@@ -1068,11 +1068,11 @@ openerp.Session = openerp.Class.extend(openerp.PropertiesMixin, {
|
||||
},
|
||||
check_session_id: function() {
|
||||
var self = this;
|
||||
if (this.avoid_recursion || self.use_cors)
|
||||
if (this.avoid_recursion)
|
||||
return $.when();
|
||||
if (this.session_id)
|
||||
return $.when(); // we already have the session id
|
||||
if (this.override_session || ! this.origin_server) {
|
||||
if (!this.use_cors && (this.override_session || ! this.origin_server)) {
|
||||
// If we don't use the origin server we consider we should always create a new session.
|
||||
// Even if some browsers could support cookies when using jsonp that behavior is
|
||||
// not consistent and the browser creators are tending to removing that feature.
|
||||
|
||||
@@ -832,7 +832,7 @@ openerp.web_calendar = function(instance) {
|
||||
}
|
||||
else {
|
||||
var pop = new instance.web.form.FormOpenPopup(this);
|
||||
pop.show_element(this.dataset.model, id, this.dataset.get_context(), {
|
||||
pop.show_element(this.dataset.model, parseInt(id), this.dataset.get_context(), {
|
||||
title: _.str.sprintf(_t("View: %s"),title),
|
||||
view_id: +this.open_popup_action,
|
||||
res_id: id,
|
||||
|
||||
@@ -48,7 +48,7 @@ class DiagramView(openerp.http.Controller):
|
||||
for tr in transitions:
|
||||
list_tr.append(tr)
|
||||
connectors.setdefault(tr, {
|
||||
'id': tr,
|
||||
'id': int(tr),
|
||||
's_id': transitions[tr][0],
|
||||
'd_id': transitions[tr][1]
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ class TableExporter(http.Controller):
|
||||
jdata = simplejson.loads(data)
|
||||
nbr_measures = jdata['nbr_measures']
|
||||
workbook = xlwt.Workbook()
|
||||
worksheet = workbook.add_sheet(jdata['title'])
|
||||
worksheet = workbook.add_sheet(jdata['title'][:30])
|
||||
header_bold = xlwt.easyxf("font: bold on; pattern: pattern solid, fore_colour gray25;")
|
||||
header_plain = xlwt.easyxf("pattern: pattern solid, fore_colour gray25;")
|
||||
bold = xlwt.easyxf("font: bold on;")
|
||||
|
||||
@@ -616,7 +616,7 @@ class website(osv.osv):
|
||||
response.data = data
|
||||
else:
|
||||
size = (max_w, max_h)
|
||||
img = image_resize_and_sharpen(image, size)
|
||||
img = image_resize_and_sharpen(image, size, preserve_aspect_ratio=True)
|
||||
image_save_for_web(img, response.stream, format=image.format)
|
||||
# invalidate content-length computed by make_conditional as
|
||||
# writing to response.stream does not do it (as of werkzeug 0.9.3)
|
||||
|
||||
@@ -131,12 +131,13 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
// generate all the controls markup
|
||||
var css = "box-sizing: border-box; position: absolute; background-color: #fff; border: 1px solid #ccc; width: 8px; height: 8px; margin-left: -4px; margin-top: -4px;";
|
||||
transfo.$markup = $(''
|
||||
+ '<div class="transfo-controls">'
|
||||
+ '<div class="transfo-container">'
|
||||
+ '<div class="transfo-controls">'
|
||||
+ '<div style="cursor: crosshair; position: absolute; margin: -30px; top: 0; right: 0; padding: 1px 0 0 1px;" class="transfo-rotator">'
|
||||
+ '<span class="fa-stack fa-lg">'
|
||||
+ '<i class="fa fa-circle fa-stack-2x"></i>'
|
||||
+ '<i class="fa fa-repeat fa-stack-1x fa-inverse"></i>'
|
||||
+ '</span>'
|
||||
+ '<span class="fa-stack fa-lg">'
|
||||
+ '<i class="fa fa-circle fa-stack-2x"></i>'
|
||||
+ '<i class="fa fa-repeat fa-stack-1x fa-inverse"></i>'
|
||||
+ '</span>'
|
||||
+ '</div>'
|
||||
+ '<div style="' + css + 'top: 0%; left: 0%; cursor: nw-resize;" class="transfo-scaler-tl"></div>'
|
||||
+ '<div style="' + css + 'top: 0%; left: 100%; cursor: ne-resize;" class="transfo-scaler-tr"></div>'
|
||||
@@ -147,6 +148,7 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
+ '<div style="' + css + 'top: 50%; left: 0%; cursor: w-resize;" class="transfo-scaler-ml"></div>'
|
||||
+ '<div style="' + css + 'top: 50%; left: 100%; cursor: e-resize;" class="transfo-scaler-mr"></div>'
|
||||
+ '<div style="' + css + 'border: 0; width: 0px; height: 0px; top: 50%; left: 50%;" class="transfo-scaler-mc"></div>'
|
||||
+ '</div>'
|
||||
+ '</div>');
|
||||
transfo.$center = transfo.$markup.find(".transfo-scaler-mc");
|
||||
|
||||
@@ -165,12 +167,20 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
_bind($this, transfo);
|
||||
|
||||
_targetCss($this, transfo);
|
||||
_stop_animation($this[0]);
|
||||
}
|
||||
|
||||
function _overwriteOptions ($this, transfo, settings) {
|
||||
transfo.settings = $.extend(transfo.settings, settings || {});
|
||||
}
|
||||
|
||||
function _stop_animation (target) {
|
||||
target.style.webkitAnimationPlayState = "paused";
|
||||
target.style.animationPlayState = "paused";
|
||||
target.style.webkitTransition = "none";
|
||||
target.style.transition = "none";
|
||||
}
|
||||
|
||||
function _setOptions ($this, transfo) {
|
||||
var style = $this.attr("style") || "";
|
||||
var transform = style.match(/transform\s*:([^;]+)/) ? style.match(/transform\s*:([^;]+)/)[1] : "";
|
||||
@@ -182,7 +192,10 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
transfo.settings.scaley= transform.indexOf('scaleY') != -1 ? parseFloat(transform.match(/scaleY\(([^)]+)\)/)[1]) : 1;
|
||||
|
||||
transfo.settings.style = style.replace(/[^;]*transform[^;]+/g, '').replace(/;+/g, ';');
|
||||
|
||||
$this.attr("style", transfo.settings.style);
|
||||
_stop_animation($this[0]);
|
||||
transfo.settings.pos = $this.offset();
|
||||
|
||||
transfo.settings.height = $this.innerHeight();
|
||||
transfo.settings.width = $this.innerWidth();
|
||||
@@ -205,7 +218,6 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
}
|
||||
|
||||
transfo.settings.css = window.getComputedStyle($this[0], null);
|
||||
transfo.settings.pos = $this.offset();
|
||||
|
||||
transfo.settings.rotationStep = 5;
|
||||
transfo.settings.hide = false;
|
||||
@@ -226,7 +238,7 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
}
|
||||
|
||||
transfo.$markup.off().on("mousedown", mousedown);
|
||||
transfo.$markup.find(">:not(.transfo-scaler-mc)").off().on("mousedown", mousedown);
|
||||
transfo.$markup.find(".transfo-controls >:not(.transfo-scaler-mc)").off().on("mousedown", mousedown);
|
||||
}
|
||||
|
||||
function _mouseDown($this, div, transfo, event) {
|
||||
@@ -339,6 +351,7 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
settings.scaley = Math.round(settings.scaley*100)/100;
|
||||
|
||||
_targetCss($this, transfo);
|
||||
_stop_animation($this[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -394,15 +407,22 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
_setCss($this, settings.style, settings);
|
||||
|
||||
_setCss(transfo.$markup,
|
||||
"position: absolute;" +
|
||||
"top:" + settings.pos.top + "px;" +
|
||||
"left:" + settings.pos.left + "px;" +
|
||||
transfo.$markup.css({
|
||||
"position": "absolute",
|
||||
"width": width + "px",
|
||||
"height": height + "px",
|
||||
"top": settings.pos.top + "px",
|
||||
"left": settings.pos.left + "px"
|
||||
});
|
||||
|
||||
var $controls = transfo.$markup.find('.transfo-controls');
|
||||
_setCss($controls,
|
||||
"width:" + width + "px;" +
|
||||
"height:" + height + "px;" +
|
||||
"cursor: move;",
|
||||
settings);
|
||||
transfo.$markup.find(">").css("transform", "scaleX("+(1/settings.scalex)+") scaleY("+(1/settings.scaley)+")");
|
||||
|
||||
$controls.children().css("transform", "scaleX("+(1/settings.scalex)+") scaleY("+(1/settings.scaley)+")");
|
||||
|
||||
_showHide($this, transfo);
|
||||
|
||||
@@ -412,10 +432,10 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
function _showHide ($this, transfo) {
|
||||
transfo.$markup.css("z-index", transfo.settings.hide ? -1 : 1000);
|
||||
if (transfo.settings.hide) {
|
||||
transfo.$markup.find(">").hide();
|
||||
transfo.$markup.find(".transfo-controls > *").hide();
|
||||
transfo.$markup.find(".transfo-scaler-mc").show();
|
||||
} else {
|
||||
transfo.$markup.find(">").show();
|
||||
transfo.$markup.find(".transfo-controls > *").show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1472,10 +1472,11 @@
|
||||
this.$target.transfo({
|
||||
hide: true,
|
||||
callback: function () {
|
||||
var pos = $(this).data("transfo").$center.offset();
|
||||
var center = $(this).data("transfo").$markup.find('.transfo-scaler-mc').offset();
|
||||
var $option = self.$overlay.find('.btn-group:first');
|
||||
self.$overlay.css({
|
||||
'top': pos.top,
|
||||
'left': pos.left,
|
||||
'top': center.top - $option.height()/2,
|
||||
'left': center.left,
|
||||
'position': 'absolute',
|
||||
});
|
||||
self.$overlay.find(".oe_overlay_options").attr("style", "width:0; left:0!important; top:0;");
|
||||
|
||||
@@ -88,6 +88,9 @@ class BlogPost(osv.Model):
|
||||
'res.users', 'Last Contributor',
|
||||
select=True, readonly=True,
|
||||
),
|
||||
'author_avatar': fields.related(
|
||||
'author_id', 'image_small',
|
||||
string="Avatar", type="binary"),
|
||||
'visits': fields.integer('No of Views'),
|
||||
'ranking': fields.function(_compute_ranking, string='Ranking', type='float'),
|
||||
}
|
||||
|
||||
@@ -131,11 +131,7 @@
|
||||
</div>
|
||||
|
||||
<div t-foreach="blog_posts" t-as="blog_post" class="mb32">
|
||||
|
||||
<img class="img-circle pull-right mt16"
|
||||
t-att-src="website.image_url(blog_post.author_id, 'image_small')"
|
||||
style="width: 50px;"/>
|
||||
|
||||
<span t-field="blog_post.author_avatar" t-field-options='{"widget": "image", "class": "img-circle pull-right mt16 media-object"}' />
|
||||
<a t-attf-href="/blog/#{ slug(blog_post.blog_id) }/post/#{ slug(blog_post) }">
|
||||
<h2 t-field="blog_post.name" class="mb4"/>
|
||||
</a>
|
||||
|
||||
@@ -369,7 +369,6 @@ class website_sale(http.Controller):
|
||||
shipping_ids = []
|
||||
checkout = {}
|
||||
if not data:
|
||||
print request.uid, request.website.user_id.id
|
||||
if request.uid != request.website.user_id.id:
|
||||
checkout.update( self.checkout_parse("billing", partner) )
|
||||
shipping_ids = orm_partner.search(cr, SUPERUSER_ID, [("parent_id", "=", partner.id), ('type', "=", 'delivery')], context=context)
|
||||
@@ -434,10 +433,10 @@ class website_sale(http.Controller):
|
||||
|
||||
return values
|
||||
|
||||
mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id", "zip"]
|
||||
optional_billing_fields = ["street", "state_id", "vat", "vat_subjected"]
|
||||
mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id", "zip"]
|
||||
optional_shipping_fields = ["state_id"]
|
||||
mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id"]
|
||||
optional_billing_fields = ["street", "state_id", "vat", "vat_subjected", "zip"]
|
||||
mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id"]
|
||||
optional_shipping_fields = ["state_id", "zip"]
|
||||
|
||||
def checkout_parse(self, address_type, data, remove_prefix=False):
|
||||
""" data is a dict OR a partner browse record
|
||||
@@ -458,7 +457,7 @@ class website_sale(http.Controller):
|
||||
else:
|
||||
query = dict((prefix + field_name, getattr(data, field_name))
|
||||
for field_name in all_fields if getattr(data, field_name))
|
||||
if data.parent_id:
|
||||
if address_type == 'billing' and data.parent_id:
|
||||
query[prefix + 'street'] = data.parent_id.name
|
||||
|
||||
if query.get(prefix + 'state_id'):
|
||||
|
||||
@@ -341,6 +341,11 @@ label.css_attribute_color.css_not_available {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.oe_website_sale input.js_quantity {
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---- Publish managment and options ---- */
|
||||
.oe_overlay_options .dropdown ul[name="size"] table {
|
||||
margin-left: 20px;
|
||||
|
||||
@@ -287,6 +287,10 @@ label.css_attribute_color.css_not_available
|
||||
.discount .oe_default_price
|
||||
display: block
|
||||
|
||||
.oe_website_sale input.js_quantity
|
||||
min-width: 48px
|
||||
text-align: center
|
||||
|
||||
/* ---- Publish managment and options ---- */
|
||||
|
||||
.oe_overlay_options
|
||||
|
||||
@@ -7,10 +7,12 @@ $('.oe_website_sale').each(function () {
|
||||
var value = +$shippingDifferent.val();
|
||||
var data = $shippingDifferent.find("option:selected").data();
|
||||
var $snipping = $(".js_shipping", oe_website_sale);
|
||||
var $inputs = $snipping.find("input,select");
|
||||
var $inputs = $snipping.find("input");
|
||||
var $selects = $snipping.find("select");
|
||||
|
||||
$snipping.toggle(!!value);
|
||||
$inputs.attr("readonly", value <= 0 ? null : "readonly" ).prop("readonly", value <= 0 ? null : "readonly" );
|
||||
$selects.attr("disabled", value <= 0 ? null : "disabled" ).prop("disabled", value <= 0 ? null : "disabled" );
|
||||
|
||||
$inputs.each(function () {
|
||||
$(this).val( data[$(this).attr("name")] || "" );
|
||||
@@ -95,7 +97,7 @@ $('.oe_website_sale').each(function () {
|
||||
$parent.find(".oe_default_price:first .oe_currency_value").html( price_to_str(+$(this).data('lst_price')) );
|
||||
$parent.find(".oe_price:first .oe_currency_value").html(price_to_str(+$(this).data('price')) );
|
||||
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img, img.product_detail_img');
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/website/image/product.product/" + $(this).val() + "/image");
|
||||
});
|
||||
|
||||
@@ -129,7 +131,7 @@ $('.oe_website_sale').each(function () {
|
||||
}
|
||||
|
||||
if (product_id) {
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img, img.product_detail_img');
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/website/image/product.product/" + product_id + "/image");
|
||||
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
|
||||
.data('oe-model', 'product.product').data('oe-id', product_id);
|
||||
|
||||
@@ -823,7 +823,7 @@
|
||||
<input type="text" name="city" class="form-control" t-att-value="checkout.get('city')"/>
|
||||
</div>
|
||||
<div t-attf-class="form-group #{error.get('zip') and 'has-error' or ''} col-lg-6">
|
||||
<label class="control-label" for="zip">Zip / Postal Code</label>
|
||||
<label class="control-label" for="zip" style="font-weight: normal">Zip / Postal Code</label>
|
||||
<input type="text" name="zip" class="form-control" t-att-value="checkout.get('zip')"/>
|
||||
</div>
|
||||
<div t-attf-class="form-group #{error.get('country_id') and 'has-error' or ''} col-lg-6">
|
||||
@@ -862,7 +862,7 @@
|
||||
t-att-data-shipping_country_id="shipping.country_id and shipping.country_id.id"
|
||||
><t t-esc="', '.join('\n'.join(shipping.name_get()[0][1].split(',')).split('\n')[1:])"/></option>
|
||||
</t>
|
||||
<option value="-1">-- Create a new address --</option>
|
||||
<option value="-1" t-att-selected="error and len(error) > 0 and shipping_id == -1">-- Create a new address --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -887,12 +887,12 @@
|
||||
<input type="text" name="shipping_city" class="form-control" t-att-value="checkout.get('shipping_city', '')" t-att-readonly=" 'readonly' if shipping_id >= 0 else ''"/>
|
||||
</div>
|
||||
<div t-attf-class="form-group #{error.get('shipping_zip') and 'has-error' or ''} col-lg-6">
|
||||
<label class="control-label" for="shipping_zip">Zip / Postal Code</label>
|
||||
<label class="control-label" for="shipping_zip" style="font-weight: normal">Zip / Postal Code</label>
|
||||
<input type="text" name="shipping_zip" class="form-control" t-att-value="checkout.get('shipping_zip', '')" t-att-readonly=" 'readonly' if shipping_id >= 0 else ''"/>
|
||||
</div>
|
||||
<div t-attf-class="form-group #{error.get('shipping_country_id') and 'has-error' or ''} col-lg-6">
|
||||
<label class="control-label" for="shipping_country_id">Country</label>
|
||||
<select name="shipping_country_id" class="form-control" t-att-readonly=" 'readonly' if shipping_id >= 0 else ''">
|
||||
<select name="shipping_country_id" class="form-control" t-att-disabled=" 'disabled' if shipping_id >= 0 else ''">
|
||||
<option value="">Country...</option>
|
||||
<t t-foreach="countries or []" t-as="country">
|
||||
<option t-att-value="country.id" t-att-selected="country.id == checkout.get('shipping_country_id')"><t t-esc="country.name"/></option>
|
||||
|
||||
@@ -53,7 +53,7 @@ $(document).ready(function () {
|
||||
$(".js_remove .js_items").addClass("hidden");
|
||||
$(".js_remove .js_item").removeClass("hidden");
|
||||
} else {
|
||||
$(".js_remove .js_items").removeClass("hidden").text($(".js_remove .js_items").text().replace(/[0-9.,]+/, qty));
|
||||
$(".js_remove .js_items").removeClass("hidden").text($(".js_remove .js_items:first").text().replace(/[0-9.,]+/, qty));
|
||||
$(".js_remove .js_item").addClass("hidden");
|
||||
}
|
||||
});
|
||||
@@ -65,5 +65,14 @@ $(document).ready(function () {
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('#cart_products input.js_quantity').change(function () {
|
||||
var value = $(this).val();
|
||||
var $next = $(this).closest('tr').next('.optional_product');
|
||||
while($next.length) {
|
||||
$next.find('.js_quantity').text(value);
|
||||
$next = $next.next('.optional_product');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
<a href="#" class="js_remove"><small>Remove from cart</small></a>
|
||||
</span>
|
||||
</td>
|
||||
<t t-set="option_inc" t-value="option_inc+1"/>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
|
||||
Vendored
+5
@@ -25,3 +25,8 @@ class Exercise(admonitions.BaseAdmonition):
|
||||
|
||||
from sphinx.locale import admonitionlabels, l_
|
||||
admonitionlabels['exercise'] = l_('Exercise')
|
||||
|
||||
# monkeypatch PHP lexer to not require <?php
|
||||
from sphinx.highlighting import lexers
|
||||
from pygments.lexers.web import PhpLexer
|
||||
lexers['php'] = PhpLexer(startinline=True)
|
||||
|
||||
Vendored
+12
-1
@@ -21,7 +21,7 @@
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block content -%}
|
||||
<div class="document-super">
|
||||
<div class="document-super {% if meta is defined %}{{ meta.classes }}{% endif %}">
|
||||
{{ super() }}
|
||||
</div>
|
||||
{%- endblock -%}
|
||||
@@ -113,4 +113,15 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{%- if google_analytics_key -%}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{ google_analytics_key }}', 'auto');
|
||||
ga('send','pageview');
|
||||
</script>
|
||||
{%- endif -%}
|
||||
{%- endblock %}
|
||||
|
||||
Vendored
+70
@@ -25,6 +25,76 @@ $(function () {
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// lang switcher
|
||||
function findSheet(pattern, fromSheet) {
|
||||
if (fromSheet) {
|
||||
for(var i=0; i<fromSheet.cssRules.length; ++i) {
|
||||
var rule = fromSheet.cssRules[i];
|
||||
if (rule.type !== CSSRule.IMPORT_RULE) { continue; }
|
||||
if (pattern.test(rule.href)) {
|
||||
return rule.styleSheet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var sheets = document.styleSheets;
|
||||
for(var j=0; j<sheets.length; ++j) {
|
||||
var sheet = sheets[j];
|
||||
if (pattern.test(sheet.href)) {
|
||||
return sheet;
|
||||
}
|
||||
var subSheet;
|
||||
if (subSheet = findSheet(pattern, sheet)) {
|
||||
return subSheet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function buildSwitcher(languages) {
|
||||
var root = document.createElement('ul');
|
||||
root.className = "switcher";
|
||||
for(var i=0; i<languages.length; ++i) {
|
||||
var item = document.createElement('li');
|
||||
item.textContent = languages[i];
|
||||
if (i === 0) {
|
||||
item.className = "active";
|
||||
}
|
||||
root.appendChild(item);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
if ($('div.document-super').hasClass('stripe')) { (function () {
|
||||
var sheet = findSheet(/style\.css$/);
|
||||
if (!sheet) { return; }
|
||||
|
||||
// collect languages
|
||||
var languages = {};
|
||||
$('div.switchable').each(function () {
|
||||
var classes = this.className.split(/\s+/);
|
||||
for (var i = 0; i < classes.length; ++i) {
|
||||
var cls = classes[i];
|
||||
if (!/^highlight-/.test(cls)) { continue; }
|
||||
languages[cls.slice(10)] = true;
|
||||
}
|
||||
});
|
||||
|
||||
$(buildSwitcher(Object.keys(languages)))
|
||||
.prependTo('div.documentwrapper')
|
||||
.on('click', 'li', function (e) {
|
||||
$(e.target).addClass('active')
|
||||
.siblings().removeClass('active');
|
||||
var id = e.target.textContent;
|
||||
var lastIndex = sheet.cssRules.length - 1;
|
||||
var content = sheet.cssRules[lastIndex].style.cssText;
|
||||
var sel = [
|
||||
'.stripe .only-', id, ', ',
|
||||
'.stripe .highlight-', id, ' > .highlight'
|
||||
].join('');
|
||||
sheet.deleteRule(lastIndex);
|
||||
sheet.insertRule(sel + '{' + content + '}', lastIndex);
|
||||
});
|
||||
})(); }
|
||||
|
||||
// Config ZeroClipboard
|
||||
ZeroClipboard.config({
|
||||
moviePath: '_static/ZeroClipboard.swf',
|
||||
|
||||
Vendored
+105
-22
@@ -6174,7 +6174,11 @@ button.close {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
.document-super {
|
||||
@@ -6643,20 +6647,11 @@ div.section > h2 {
|
||||
*
|
||||
* Generated via Pygments
|
||||
*/
|
||||
.highlight {
|
||||
padding: 9px 14px;
|
||||
margin-bottom: 14px;
|
||||
background-color: #f7f7f9 !important;
|
||||
border: 1px solid #e1e1e8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.highlight pre {
|
||||
color: #333;
|
||||
padding: 0 45px 0 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
padding: 4px;
|
||||
font-size: 75%;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
}
|
||||
/*
|
||||
* ZeroClipboard styles
|
||||
@@ -6665,6 +6660,11 @@ div.section > h2 {
|
||||
position: relative;
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.zero-clipboard {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.btn-clipboard {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -6684,11 +6684,6 @@ div.section > h2 {
|
||||
background-color: #a24689;
|
||||
border-color: #a24689;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.zero-clipboard {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
img.align-center {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
@@ -6710,10 +6705,98 @@ td.field-body > ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
pre {
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
}
|
||||
.descclassname {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.stripe .section {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.stripe .section > *,
|
||||
.stripe .section > .force-left {
|
||||
width: 49%;
|
||||
float: left;
|
||||
clear: left;
|
||||
}
|
||||
.stripe .section > .force-right,
|
||||
.stripe .section > [class*=highlight] {
|
||||
float: none;
|
||||
clear: none;
|
||||
margin-left: 51%;
|
||||
}
|
||||
.stripe .section > h1,
|
||||
.stripe .section > h2,
|
||||
.stripe .section > h3,
|
||||
.stripe .section > h4,
|
||||
.stripe .section > h5,
|
||||
.stripe .section > h6 {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
.stripe .section > h1,
|
||||
.stripe .section > h2,
|
||||
.stripe .section > h3,
|
||||
.stripe .section > h4,
|
||||
.stripe .section > h5,
|
||||
.stripe .section > h6,
|
||||
.stripe .section > .section {
|
||||
position: relative;
|
||||
width: auto;
|
||||
float: none;
|
||||
clear: both;
|
||||
}
|
||||
.stripe .bodywrapper {
|
||||
position: relative;
|
||||
}
|
||||
.stripe .bodywrapper:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
content: "";
|
||||
width: 0;
|
||||
border-left: 1px solid #777777;
|
||||
}
|
||||
}
|
||||
.stripe .switcher {
|
||||
color: white;
|
||||
width: auto !important;
|
||||
float: none !important;
|
||||
position: fixed;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-justify-content: flex-end;
|
||||
justify-content: flex-end;
|
||||
right: 0.5em;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
.stripe .switcher li {
|
||||
background-color: #0f131a;
|
||||
padding: 0.4em 1em;
|
||||
border: 1px solid #333;
|
||||
border-left-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.stripe .switcher li:first-child {
|
||||
border-left-width: 1px;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.stripe .switcher li:last-child {
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
.stripe .switcher li:hover {
|
||||
background-color: #222;
|
||||
}
|
||||
.stripe .switcher li.active {
|
||||
background-color: #333;
|
||||
}
|
||||
.stripe [class*=only-],
|
||||
.stripe .switchable > .highlight {
|
||||
display: none;
|
||||
}
|
||||
.stripe .only-python,
|
||||
.stripe .highlight-python > .highlight {
|
||||
display: block;
|
||||
}
|
||||
|
||||
+113
-26
@@ -17,7 +17,11 @@
|
||||
// indent level for various items list e.g. dl, fields lists, ...
|
||||
@item-indent: 30px;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -461,21 +465,13 @@ div.section > h2 {
|
||||
*
|
||||
* Generated via Pygments
|
||||
*/
|
||||
|
||||
.highlight {
|
||||
padding: 9px 14px;
|
||||
margin-bottom: 14px;
|
||||
background-color: #f7f7f9 !important;
|
||||
border: 1px solid #e1e1e8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.highlight pre {
|
||||
color: #333;
|
||||
padding: 0 45px 0 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
padding: 4px;
|
||||
|
||||
font-size: 75%;
|
||||
// code block lines should not wrap
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -485,6 +481,9 @@ div.section > h2 {
|
||||
.zero-clipboard {
|
||||
position: relative;
|
||||
display: none;
|
||||
@media (min-width: @screen-sm-min) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.btn-clipboard {
|
||||
position: absolute;
|
||||
@@ -506,12 +505,6 @@ div.section > h2 {
|
||||
border-color: @brand-primary;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.zero-clipboard {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// rST styles
|
||||
img.align-center {
|
||||
display: block;
|
||||
@@ -546,13 +539,107 @@ td.field-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// code block lines should not wrap
|
||||
pre {
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
// lighten js namespace/class name
|
||||
.descclassname {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
// STRIPE-STYLE PAGES
|
||||
.stripe {
|
||||
.section {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
// === columning only on medium+ ===
|
||||
@media (min-width: @screen-md-min) {
|
||||
// column 1
|
||||
.section > *,
|
||||
.section > .force-left {
|
||||
width: 49%;
|
||||
float: left;
|
||||
clear: left;
|
||||
}
|
||||
// column 2
|
||||
.section > .force-right,
|
||||
.section > [class*=highlight] {
|
||||
float: none;
|
||||
clear: none;
|
||||
margin-left: 51%;
|
||||
}
|
||||
// fullwidth elements
|
||||
.section > h1, .section > h2, .section > h3, .section > h4, .section > h5,
|
||||
.section > h6 {
|
||||
background-color: fadeout(@body-bg, 30%);
|
||||
}
|
||||
.section > h1, .section > h2, .section > h3, .section > h4, .section > h5,
|
||||
.section > h6, .section > .section {
|
||||
position: relative;
|
||||
width: auto;
|
||||
float: none;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.bodywrapper {
|
||||
position: relative;
|
||||
// middle separator
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
content: "";
|
||||
width: 0;
|
||||
border-left: 1px solid @gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switcher {
|
||||
color: white;
|
||||
width: auto !important;
|
||||
float: none !important;
|
||||
|
||||
position: fixed;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-justify-content: flex-end;
|
||||
justify-content: flex-end;
|
||||
|
||||
right: 0.5em;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: 5;
|
||||
|
||||
li {
|
||||
background-color: #0f131a;
|
||||
padding: 0.4em 1em;
|
||||
border: 1px solid #333;
|
||||
border-left-width: 0;
|
||||
cursor: pointer;
|
||||
&:first-child {
|
||||
border-left-width: 1px;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
&:last-child {
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #222;
|
||||
}
|
||||
&.active {
|
||||
background-color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === show/hide code snippets ===
|
||||
[class*=only-],
|
||||
.switchable > .highlight {
|
||||
display: none;
|
||||
}
|
||||
// must be final rule of page
|
||||
.only-python, .highlight-python > .highlight {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,9 @@ def setup(app):
|
||||
app.connect('html-page-context', versionize)
|
||||
app.add_config_value('versions', '', 'env')
|
||||
|
||||
app.connect('html-page-context', analytics)
|
||||
app.add_config_value('google_analytics_key', False, 'env')
|
||||
|
||||
def canonicalize(app, pagename, templatename, context, doctree):
|
||||
""" Adds a 'canonical' URL for the current document in the rendering
|
||||
context. Requires the ``canonical_root`` setting being set. The canonical
|
||||
@@ -212,6 +215,12 @@ def versionize(app, pagename, templatename, context, doctree):
|
||||
if vs != app.config.version
|
||||
]
|
||||
|
||||
def analytics(app, pagename, templatename, context, doctree):
|
||||
if not app.config.google_analytics_key:
|
||||
return
|
||||
|
||||
context['google_analytics_key'] = app.config.google_analytics_key
|
||||
|
||||
def _build_url(root, branch, pagename):
|
||||
return "{canonical_url}{canonical_branch}/{canonical_page}".format(
|
||||
canonical_url=root,
|
||||
|
||||
@@ -5,3 +5,4 @@ Module Objects
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
modules/api_integration
|
||||
|
||||
@@ -0,0 +1,816 @@
|
||||
:classes: stripe
|
||||
|
||||
===========
|
||||
Odoo as API
|
||||
===========
|
||||
|
||||
Odoo is mostly extended internally via modules, but much of its features and
|
||||
all of its data is also available from the outside for external analysis or
|
||||
integration with various tools. Part of the :ref:`reference/orm/model` API is
|
||||
easily available over XML-RPC_ and accessible from a variety of languages.
|
||||
|
||||
.. Odoo XML-RPC idiosyncracies:
|
||||
* uses multiple endpoint and a nested call syntax instead of a
|
||||
"hierarchical" server structure (e.g. ``openerp.res.partner.read()``)
|
||||
* uses its own own manual auth system instead of basic auth or sessions
|
||||
(basic is directly supported the Python and Ruby stdlibs as well as
|
||||
ws-xmlrpc, not sure about ripcord)
|
||||
* own auth is inconvenient as (uid, password) have to be explicitly passed
|
||||
into every call. Session would allow db to be stored as well
|
||||
These issues are especially visible in Java, somewhat less so in PHP
|
||||
|
||||
Connection and authentication
|
||||
=============================
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
If you already have an Odoo server installed, you can just use its
|
||||
parameters
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url = <insert server URL>
|
||||
db = <insert database name>
|
||||
username = 'admin'
|
||||
password = <insert password for your admin user (default: admin)>
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
url = <insert server URL>
|
||||
db = <insert database name>
|
||||
username = "admin"
|
||||
password = <insert password for your admin user (default: admin)>
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$url = <insert server URL>;
|
||||
$db = <insert database name>;
|
||||
$username = "admin";
|
||||
$password = <insert password for your admin user (default: admin)>;
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final String url = <insert server URL>,
|
||||
db = <insert database name>,
|
||||
username = "admin",
|
||||
password = <insert password for your admin user (default: admin)>;
|
||||
|
||||
To make exploration simpler, you can also ask https://demo.odoo.com for a test
|
||||
database:
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import xmlrpclib
|
||||
info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
|
||||
url, db, username, password = \
|
||||
info['host'], info['database'], info['user'], info['password']
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
require "xmlrpc/client"
|
||||
info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
|
||||
url, db, username, password = \
|
||||
info['host'], info['database'], info['user'], info['password']
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
require_once('ripcord.php');
|
||||
$info = ripcord::client('https://demo.odoo.com/start')->start();
|
||||
list($url, $db, $username, $password) =
|
||||
array($info['host'], $info['database'], $info['user'], $info['password']);
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final XmlRpcClient client = new XmlRpcClient();
|
||||
|
||||
final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
|
||||
start_config.setServerURL(new URL("https://demo.odoo.com/start"));
|
||||
final Map<String, String> info = (Map<String, String>)client.execute(
|
||||
start_config, "start", Collections.emptyList());
|
||||
|
||||
final String url = info.get("host"),
|
||||
db = info.get("database"),
|
||||
username = info.get("user"),
|
||||
password = info.get("password");
|
||||
|
||||
.. rst-class:: force-right
|
||||
|
||||
.. note::
|
||||
:class: only-php
|
||||
|
||||
These examples use the `Ripcord <https://code.google.com/p/ripcord/>`_
|
||||
library, which provides a simple XML-RPC API. Ripcord requires that
|
||||
`XML-RPC support be enabled
|
||||
<http://php.net/manual/en/xmlrpc.installation.php>`_ in your PHP
|
||||
installation.
|
||||
|
||||
Since calls are performed over
|
||||
`HTTPS <http://en.wikipedia.org/wiki/HTTP_Secure>`_, it also requires that
|
||||
the `OpenSSL extension
|
||||
<http://php.net/manual/en/openssl.installation.php>`_ be enabled.
|
||||
|
||||
.. note::
|
||||
:class: only-java
|
||||
|
||||
These examples use the `Apache XML-RPC library
|
||||
<https://ws.apache.org/xmlrpc/>`_
|
||||
|
||||
Logging in
|
||||
----------
|
||||
|
||||
Odoo requires users of the API to be authenticated before being able to query
|
||||
much data.
|
||||
|
||||
The ``xmlrpc/2/common`` endpoint provides meta-calls which don't require
|
||||
authentication, such as the authentication itself or fetching version
|
||||
information. To verify if the connection information is correct before trying
|
||||
to authenticate, the simplest call is to ask for the server's version. The
|
||||
authentication itself is done through the ``authenticate`` function and
|
||||
returns a user identifier (``uid``) used in authenticated calls instead of
|
||||
the login.
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
|
||||
common.version()
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
|
||||
common.call('version')
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$common = ripcord::client("$url/xmlrpc/2/common");
|
||||
$common->version();
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
|
||||
common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
|
||||
client.execute(common_config, "version", Collections.emptyList());
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"server_version": "8.0",
|
||||
"server_version_info": [8, 0, 0, "final", 0],
|
||||
"server_serie": "8.0",
|
||||
"protocol_version": 1,
|
||||
}
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
uid = common.authenticate(db, username, password, {})
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
uid = common.call('authenticate', db, username, password, {})
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$uid = $common->authenticate($db, $username, $password, array());
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
int uid = (int)client.execute(
|
||||
common_config, "authenticate", Arrays.asList(
|
||||
db, username, password, Collections.emptyMap()));
|
||||
|
||||
Calling methods
|
||||
===============
|
||||
|
||||
The second — and most generally useful — is ``xmlrpc/2/object`` which is used
|
||||
to call methods of odoo models via the ``execute_kw`` RPC function.
|
||||
|
||||
Each call to ``execute_kw`` takes the following parameters:
|
||||
|
||||
* the database to use, a string
|
||||
* the user id (retrieved through ``authenticate``), an integer
|
||||
* the user's password, a string
|
||||
* the model name, a string
|
||||
* the method name, a string
|
||||
* an array/list of parameters passed by position
|
||||
* a mapping/dict of parameters to pass by keyword (optional)
|
||||
|
||||
.. rst-class:: force-right
|
||||
|
||||
For instance to see if we can read the ``res.partner`` model we can call
|
||||
``check_access_rights`` with ``operation`` passed by position and
|
||||
``raise_exception`` passed by keyword (in order to get a true/false result
|
||||
rather than true/error):
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'check_access_rights',
|
||||
['read'], {'raise_exception': False})
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'check_access_rights',
|
||||
['read'], {raise_exception: false})
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$models = ripcord::client("$url/xmlrpc/2/object");
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'check_access_rights',
|
||||
array('read'), array('raise_exception' => false));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final XmlRpcClient models = new XmlRpcClient() {{
|
||||
setConfig(new XmlRpcClientConfigImpl() {{
|
||||
setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
|
||||
}});
|
||||
}};
|
||||
models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "check_access_rights",
|
||||
Arrays.asList("read"),
|
||||
new HashMap() {{ put("raise_exception", false); }}
|
||||
));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
true
|
||||
|
||||
.. todo:: this should be runnable and checked
|
||||
|
||||
List records
|
||||
------------
|
||||
|
||||
Records can be listed and filtered via :meth:`~openerp.models.Model.search`.
|
||||
|
||||
:meth:`~openerp.models.Model.search` takes a mandatory
|
||||
:ref:`domain <reference/orm/domains>` filter (possibly empty), and returns the
|
||||
database identifiers of all records matching the filter. To list customer
|
||||
companies for instance:
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search',
|
||||
[[['is_company', '=', True], ['customer', '=', True]]])
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search',
|
||||
[[['is_company', '=', true], ['customer', '=', true]]])
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$domain = array(array('is_company', '=', true),
|
||||
array('customer', '=', true));
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'search', array($domain));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final List domain = Arrays.asList(
|
||||
Arrays.asList("is_company", "=", true),
|
||||
Arrays.asList("customer", "=", true));
|
||||
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "search",
|
||||
Arrays.asList(domain)
|
||||
)));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
|
||||
|
||||
Pagination
|
||||
''''''''''
|
||||
|
||||
By default a research will return the ids of all records matching the
|
||||
condition, which may be a huge number. ``offset`` and ``limit`` parameters are
|
||||
available to only retrieve a subset of all matched records.
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search',
|
||||
[[['is_company', '=', True], ['customer', '=', True]]],
|
||||
{'offset': 10, 'limit': 5})
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search',
|
||||
[[['is_company', '=', true], ['customer', '=', true]]],
|
||||
{offset: 10, limit: 5})
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'search',
|
||||
array($domain),
|
||||
array('offset'=>10, 'limit'=>5));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "search",
|
||||
Arrays.asList(domain),
|
||||
new HashMap() {{ put("offset", 10); put("limit", 5); }}
|
||||
)));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[13, 20, 30, 22, 29]
|
||||
|
||||
Count records
|
||||
-------------
|
||||
|
||||
Rather than retrieve a possibly gigantic list of records and count them
|
||||
afterwards, :meth:`~openerp.models.Model.search_count` can be used to retrieve
|
||||
only the number of records matching the query. It takes the same
|
||||
:ref:`domain <reference/orm/domains>` filter as
|
||||
:meth:`~openerp.models.Model.search` and no other parameter.
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search_count',
|
||||
[[['is_company', '=', True], ['customer', '=', True]]])
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search_count',
|
||||
[[['is_company', '=', true], ['customer', '=', true]]])
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'search_count',
|
||||
array($domain));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
(Integer)models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "search_count",
|
||||
Arrays.asList(domain)
|
||||
));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
19
|
||||
|
||||
.. warning::
|
||||
|
||||
calling ``search`` then ``search_count`` (or the other way around) may not
|
||||
yield coherent results if other users are using the server: stored data
|
||||
could have changed between the calls
|
||||
|
||||
Read records
|
||||
------------
|
||||
|
||||
Record data is accessible via the :meth:`~openerp.models.Model.read` method,
|
||||
which takes a list of ids (as returned by
|
||||
:meth:`~openerp.models.Model.search`) and optionally a list of fields to
|
||||
fetch. By default, it will fetch all the fields the current user can read,
|
||||
which tends to be a huge amount.
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ids = models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search',
|
||||
[[['is_company', '=', True], ['customer', '=', True]]],
|
||||
{'limit': 1})
|
||||
[record] = models.execute_kw(db, uid, password,
|
||||
'res.partner', 'read', [ids])
|
||||
# count the number of fields fetched by default
|
||||
len(record)
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
ids = models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search',
|
||||
[[['is_company', '=', true], ['customer', '=', true]]],
|
||||
{limit: 1})
|
||||
record = models.execute_kw(db, uid, password,
|
||||
'res.partner', 'read', [ids]).first
|
||||
# count the number of fields fetched by default
|
||||
record.length
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ids = $models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'search',
|
||||
array($domain),
|
||||
array('limit'=>1));
|
||||
$records = $models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'read', array($ids));
|
||||
// count the number of fields fetched by default
|
||||
count($records[0]);
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final List ids = Arrays.asList((Object[])models.execute(
|
||||
"execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "search",
|
||||
Arrays.asList(domain),
|
||||
new HashMap() {{ put("limit", 1); }})));
|
||||
final Map record = (Map)((Object[])models.execute(
|
||||
"execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "read",
|
||||
Arrays.asList(ids)
|
||||
)
|
||||
))[0];
|
||||
// count the number of fields fetched by default
|
||||
record.size();
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
121
|
||||
|
||||
Conversedly, picking only three fields deemed interesting.
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'read',
|
||||
[ids], {'fields': ['name', 'country_id', 'comment']})
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'read',
|
||||
[ids], {fields: %w(name country_id comment)})
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'read',
|
||||
array($ids),
|
||||
array('fields'=>array('name', 'country_id', 'comment')));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "read",
|
||||
Arrays.asList(ids),
|
||||
new HashMap() {{
|
||||
put("fields", Arrays.asList("name", "country_id", "comment"));
|
||||
}}
|
||||
)));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
|
||||
|
||||
.. note:: even if the ``id`` field is not requested, it is always returned
|
||||
|
||||
Listing record fields
|
||||
---------------------
|
||||
|
||||
:meth:`~openerp.models.Model.fields_get` can be used to inspect
|
||||
a model's fields and check which ones seem to be of interest.
|
||||
|
||||
Because
|
||||
it returns a great amount of meta-information (it is also used by client
|
||||
programs) it should be filtered before printing, the most interesting items
|
||||
for a human user are ``string`` (the field's label), ``help`` (a help text if
|
||||
available) and ``type`` (to know which values to expect, or to send when
|
||||
updating a record):
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [])
|
||||
# filter keys of field attributes for display
|
||||
{field: {
|
||||
k: v for k, v in attributes.iteritems()
|
||||
if k in ['string', 'help', 'type']
|
||||
}
|
||||
for field, attributes in fields.iteritems()}
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [])
|
||||
# filter keys of field attributes for display
|
||||
fields.each {|k, v|
|
||||
fields[k] = v.keep_if {|kk, vv| %w(string help type).include? kk}
|
||||
}
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$fields_full = $models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'fields_get', array());
|
||||
// filter keys of field attributes for display
|
||||
$allowed = array_flip(array('string', 'help', 'type'));
|
||||
$fields = array();
|
||||
foreach($fields_full as $field => $attributes) {
|
||||
$fields[$field] = array_intersect_key($attributes, $allowed);
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final Map<String, Map<String, Object>> fields =
|
||||
(Map<String, Map<String, Object>>)models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "fields_get",
|
||||
Collections.emptyList()));
|
||||
// filter keys of field attributes for display
|
||||
final List<String> allowed = Arrays.asList("string", "help", "type");
|
||||
new HashMap<String, Map<String, Object>>() {{
|
||||
for(Entry<String, Map<String, Object>> item: fields.entrySet()) {
|
||||
put(item.getKey(), new HashMap<String, Object>() {{
|
||||
for(Entry<String, Object> it: item.getValue().entrySet()) {
|
||||
if (allowed.contains(it.getKey())) {
|
||||
put(it.getKey(), it.getValue());
|
||||
}
|
||||
}
|
||||
}});
|
||||
}
|
||||
}};
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"ean13": {
|
||||
"type": "char",
|
||||
"help": "BarCode",
|
||||
"string": "EAN13"
|
||||
},
|
||||
"property_account_position": {
|
||||
"type": "many2one",
|
||||
"help": "The fiscal position will determine taxes and accounts used for the partner.",
|
||||
"string": "Fiscal Position"
|
||||
},
|
||||
"signup_valid": {
|
||||
"type": "boolean",
|
||||
"help": "",
|
||||
"string": "Signup Token is Valid"
|
||||
},
|
||||
"date_localization": {
|
||||
"type": "date",
|
||||
"help": "",
|
||||
"string": "Geo Localization Date"
|
||||
},
|
||||
"ref_companies": {
|
||||
"type": "one2many",
|
||||
"help": "",
|
||||
"string": "Companies that refers to partner"
|
||||
},
|
||||
"sale_order_count": {
|
||||
"type": "integer",
|
||||
"help": "",
|
||||
"string": "# of Sales Order"
|
||||
},
|
||||
"purchase_order_count": {
|
||||
"type": "integer",
|
||||
"help": "",
|
||||
"string": "# of Purchase Order"
|
||||
},
|
||||
|
||||
Search and read
|
||||
---------------
|
||||
|
||||
Because that is a very common task, Odoo provides a
|
||||
:meth:`~openerp.models.Model.search_read` shortcut which as its name notes is
|
||||
equivalent to a :meth:`~openerp.models.Model.search` followed by a
|
||||
:meth:`~openerp.models.Model.read`, but avoids having to perform two requests
|
||||
and keep ids around. Its arguments are similar to
|
||||
:meth:`~openerp.models.Model.search`'s, but it can also take a list of
|
||||
``fields`` (like :meth:`~openerp.models.Model.read`, if that list is not
|
||||
provided it'll fetch all fields of matched records):
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search_read',
|
||||
[[['is_company', '=', True], ['customer', '=', True]]],
|
||||
{'fields': ['name', 'country_id', 'comment'], 'limit': 5})
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search_read',
|
||||
[[['is_company', '=', true], ['customer', '=', true]]],
|
||||
{fields: %w(name country_id comment), limit: 5})
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'search_read',
|
||||
array($domain),
|
||||
array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "search_read",
|
||||
Arrays.asList(domain),
|
||||
new HashMap() {{
|
||||
put("fields", Arrays.asList("name", "country_id", "comment"));
|
||||
put("limit", 5);
|
||||
}}
|
||||
)));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"comment": false,
|
||||
"country_id": [ 21, "Belgium" ],
|
||||
"id": 7,
|
||||
"name": "Agrolait"
|
||||
},
|
||||
{
|
||||
"comment": false,
|
||||
"country_id": [ 76, "France" ],
|
||||
"id": 18,
|
||||
"name": "Axelor"
|
||||
},
|
||||
{
|
||||
"comment": false,
|
||||
"country_id": [ 233, "United Kingdom" ],
|
||||
"id": 12,
|
||||
"name": "Bank Wealthy and sons"
|
||||
},
|
||||
{
|
||||
"comment": false,
|
||||
"country_id": [ 105, "India" ],
|
||||
"id": 14,
|
||||
"name": "Best Designers"
|
||||
},
|
||||
{
|
||||
"comment": false,
|
||||
"country_id": [ 76, "France" ],
|
||||
"id": 17,
|
||||
"name": "Camptocamp"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Create records
|
||||
--------------
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
|
||||
'name': "New Partner",
|
||||
}])
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
|
||||
name: "New Partner",
|
||||
}])
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$id = $models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'create',
|
||||
array(array('name'=>"New Partner")));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
final Integer id = (Integer)models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "create",
|
||||
Arrays.asList(new HashMap() {{ put("name", "New Partner"); }})
|
||||
));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
78
|
||||
|
||||
Update records
|
||||
--------------
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
|
||||
'name': "Newer partner"
|
||||
}])
|
||||
# get record name after having changed it
|
||||
models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
|
||||
name: "Newer partner"
|
||||
}])
|
||||
# get record name after having changed it
|
||||
models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$models->execute_kw($db, $uid, $password, 'res.partner', 'write',
|
||||
array(array($id), array('name'=>"Newer partner")));
|
||||
// get record name after having changed it
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'name_get', array(array($id)));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "write",
|
||||
Arrays.asList(
|
||||
Arrays.asList(id),
|
||||
new HashMap() {{ put("name", "Newer Partner"); }}
|
||||
)
|
||||
));
|
||||
// get record name after having changed it
|
||||
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "name_get",
|
||||
Arrays.asList(Arrays.asList(id))
|
||||
)));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[[78, "Newer partner"]]
|
||||
|
||||
Delete records
|
||||
--------------
|
||||
|
||||
.. rst-class:: switchable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
|
||||
# check if the deleted record is still in the database
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search', [[['id', '=', id]]])
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
|
||||
# check if the deleted record is still in the database
|
||||
models.execute_kw(db, uid, password,
|
||||
'res.partner', 'search', [[['id', '=', id]]])
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'unlink',
|
||||
array(array($id)));
|
||||
// check if the deleted record is still in the database
|
||||
$models->execute_kw($db, $uid, $password,
|
||||
'res.partner', 'search',
|
||||
array(array(array('id', '=', $id))));
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "unlink",
|
||||
Arrays.asList(Arrays.asList(id))));
|
||||
// check if the deleted record is still in the database
|
||||
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
|
||||
db, uid, password,
|
||||
"res.partner", "search",
|
||||
Arrays.asList(Arrays.asList(Arrays.asList("id", "=", 78)))
|
||||
)));
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[]
|
||||
|
||||
.. _PostgreSQL: http://www.postgresql.org
|
||||
.. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC
|
||||
@@ -782,7 +782,7 @@ class ir_model_access(osv.osv):
|
||||
_logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
|
||||
msg = '%s %s' % (msg_heads[mode], msg_tail)
|
||||
raise openerp.exceptions.AccessError(msg % msg_params)
|
||||
return r or False
|
||||
return bool(r)
|
||||
|
||||
__cache_clearing_methods = []
|
||||
|
||||
|
||||
+2
-1
@@ -319,7 +319,8 @@ class Field(object):
|
||||
self._free_attrs.append(attr)
|
||||
setattr(self, attr, value)
|
||||
|
||||
if not self.string:
|
||||
if not self.string and not self.related:
|
||||
# related fields get their string from their parent field
|
||||
self.string = name.replace('_', ' ').capitalize()
|
||||
|
||||
# determine self.default and cls._defaults in a consistent way
|
||||
|
||||
+7
-4
@@ -1654,7 +1654,7 @@ class BaseModel(object):
|
||||
|
||||
@api.returns('self')
|
||||
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
|
||||
""" search(args[, offset=0][, limit=None][, order=None][, count=False])
|
||||
""" search(args[, offset=0][, limit=None][, order=None])
|
||||
|
||||
Searches for records based on the ``args``
|
||||
:ref:`search domain <reference/orm/domains>`.
|
||||
@@ -1664,9 +1664,6 @@ class BaseModel(object):
|
||||
:param int offset: number of results to ignore (default: none)
|
||||
:param int limit: maximum number of records to return (default: all)
|
||||
:param str order: sort string
|
||||
:param bool count: if ``True``, the call should return the number of
|
||||
records matching ``args`` rather than the records
|
||||
themselves.
|
||||
:returns: at most ``limit`` records matching the search criteria
|
||||
|
||||
:raise AccessError: * if user tries to bypass access rules for read on the requested object.
|
||||
@@ -2996,6 +2993,12 @@ class BaseModel(object):
|
||||
if not partial:
|
||||
raise
|
||||
|
||||
# update columns (fields may have changed), and column_infos
|
||||
for name, field in self._fields.iteritems():
|
||||
if field.store:
|
||||
self._columns[name] = field.to_column()
|
||||
self._inherits_reload()
|
||||
|
||||
# group fields by compute to determine field.computed_fields
|
||||
fields_by_compute = defaultdict(list)
|
||||
for field in self._fields.itervalues():
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
|
||||
"""
|
||||
from collections import Mapping
|
||||
from contextlib import contextmanager
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
import openerp
|
||||
from .. import SUPERUSER_ID
|
||||
from openerp.tools import assertion_report, lazy_property
|
||||
from openerp.tools import assertion_report, lazy_property, classproperty, config
|
||||
from openerp.tools.lru import LRU
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -259,12 +260,27 @@ class RegistryManager(object):
|
||||
registries (essentially database connection/model registry pairs).
|
||||
|
||||
"""
|
||||
# Mapping between db name and model registry.
|
||||
# Accessed through the methods below.
|
||||
registries = {}
|
||||
_registries = None
|
||||
_lock = threading.RLock()
|
||||
_saved_lock = None
|
||||
|
||||
@classproperty
|
||||
def registries(cls):
|
||||
if cls._registries is None:
|
||||
size = config.get('registry_lru_size', None)
|
||||
if not size:
|
||||
# Size the LRU depending of the memory limits
|
||||
if os.name != 'posix':
|
||||
# cannot specify the memory limit soft on windows...
|
||||
size = 42
|
||||
else:
|
||||
# On average, a clean registry take 25MB of memory + cache
|
||||
avgsz = 30 * 1024 * 1024
|
||||
size = int(config['limit_memory_soft'] / avgsz)
|
||||
|
||||
cls._registries = LRU(size)
|
||||
return cls._registries
|
||||
|
||||
@classmethod
|
||||
def lock(cls):
|
||||
""" Return the current registry lock. """
|
||||
|
||||
+47
-29
@@ -83,7 +83,28 @@ class _column(object):
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = None
|
||||
_deprecated = False
|
||||
copy = True # whether the field is copied by BaseModel.copy()
|
||||
|
||||
copy = True # whether value is copied by BaseModel.copy()
|
||||
string = None
|
||||
help = ""
|
||||
required = False
|
||||
readonly = False
|
||||
_domain = []
|
||||
_context = {}
|
||||
states = None
|
||||
priority = 0
|
||||
change_default = False
|
||||
size = None
|
||||
ondelete = None
|
||||
translate = False
|
||||
select = False
|
||||
manual = False
|
||||
write = False
|
||||
read = False
|
||||
selectable = True
|
||||
group_operator = False
|
||||
groups = False # CSV list of ext IDs of groups
|
||||
deprecated = False # Optional deprecation warning
|
||||
|
||||
def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args):
|
||||
"""
|
||||
@@ -92,33 +113,28 @@ class _column(object):
|
||||
It corresponds to the 'state' column in ir_model_fields.
|
||||
|
||||
"""
|
||||
if domain is None:
|
||||
domain = []
|
||||
if context is None:
|
||||
context = {}
|
||||
self.states = states or {}
|
||||
self.string = string
|
||||
self.readonly = readonly
|
||||
self.required = required
|
||||
self.size = size
|
||||
self.help = args.get('help', '')
|
||||
self.priority = priority
|
||||
self.change_default = change_default
|
||||
self.ondelete = ondelete.lower() if ondelete else 'set null'
|
||||
self.translate = translate
|
||||
self._domain = domain
|
||||
self._context = context
|
||||
self.write = False
|
||||
self.read = False
|
||||
self.select = select
|
||||
self.manual = manual
|
||||
self.selectable = True
|
||||
self.group_operator = args.get('group_operator', False)
|
||||
self.groups = False # CSV list of ext IDs of groups that can access this field
|
||||
self.deprecated = False # Optional deprecation warning
|
||||
args0 = {
|
||||
'string': string,
|
||||
'required': required,
|
||||
'readonly': readonly,
|
||||
'_domain': domain,
|
||||
'_context': context,
|
||||
'states': states,
|
||||
'priority': priority,
|
||||
'change_default': change_default,
|
||||
'size': size,
|
||||
'ondelete': ondelete.lower() if ondelete else None,
|
||||
'translate': translate,
|
||||
'select': select,
|
||||
'manual': manual,
|
||||
}
|
||||
for key, val in args0.iteritems():
|
||||
if val:
|
||||
setattr(self, key, val)
|
||||
|
||||
self._args = args
|
||||
for a in args:
|
||||
setattr(self, a, args[a])
|
||||
for key, val in args.iteritems():
|
||||
setattr(self, key, val)
|
||||
|
||||
# prefetch only if self._classic_write, not self.groups, and not
|
||||
# self.deprecated
|
||||
@@ -150,6 +166,8 @@ class _column(object):
|
||||
base_items = [
|
||||
('column', self), # field interfaces self
|
||||
('copy', self.copy),
|
||||
]
|
||||
truthy_items = filter(itemgetter(1), [
|
||||
('index', self.select),
|
||||
('manual', self.manual),
|
||||
('string', self.string),
|
||||
@@ -160,8 +178,6 @@ class _column(object):
|
||||
('groups', self.groups),
|
||||
('change_default', self.change_default),
|
||||
('deprecated', self.deprecated),
|
||||
]
|
||||
truthy_items = filter(itemgetter(1), [
|
||||
('size', self.size),
|
||||
('ondelete', self.ondelete),
|
||||
('translate', self.translate),
|
||||
@@ -620,6 +636,8 @@ class many2one(_column):
|
||||
_symbol_f = lambda x: x or None
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
|
||||
ondelete = 'set null'
|
||||
|
||||
def __init__(self, obj, string='unknown', auto_join=False, **args):
|
||||
_column.__init__(self, string=string, **args)
|
||||
self._obj = obj
|
||||
|
||||
@@ -18,14 +18,8 @@ RPC_VERSION_1 = {
|
||||
}
|
||||
|
||||
def dispatch(method, params):
|
||||
if method in ['login', 'about', 'timezone_get',
|
||||
'version', 'authenticate']:
|
||||
pass
|
||||
elif method in ['set_loglevel']:
|
||||
passwd = params[0]
|
||||
params = params[1:]
|
||||
security.check_super(passwd)
|
||||
else:
|
||||
if method not in ['login', 'about', 'timezone_get',
|
||||
'version', 'authenticate', 'set_loglevel']:
|
||||
raise Exception("Method not found: %s" % method)
|
||||
|
||||
fn = globals()['exp_' + method]
|
||||
|
||||
+7
-5
@@ -477,8 +477,6 @@ class ConnectionPool(object):
|
||||
|
||||
@locked
|
||||
def borrow(self, dsn):
|
||||
self._debug('Borrow connection to %r', dsn)
|
||||
|
||||
# free dead and leaked connections
|
||||
for i, (cnx, _) in tools.reverse_enumerate(self._connections):
|
||||
if cnx.closed:
|
||||
@@ -503,7 +501,7 @@ class ConnectionPool(object):
|
||||
continue
|
||||
self._connections.pop(i)
|
||||
self._connections.append((cnx, True))
|
||||
self._debug('Existing connection found at index %d', i)
|
||||
self._debug('Borrow existing connection to %r at index %d', cnx.dsn, i)
|
||||
|
||||
return cnx
|
||||
|
||||
@@ -546,11 +544,15 @@ class ConnectionPool(object):
|
||||
|
||||
@locked
|
||||
def close_all(self, dsn=None):
|
||||
_logger.info('%r: Close all connections to %r', self, dsn)
|
||||
count = 0
|
||||
last = None
|
||||
for i, (cnx, used) in tools.reverse_enumerate(self._connections):
|
||||
if dsn is None or cnx._original_dsn == dsn:
|
||||
cnx.close()
|
||||
self._connections.pop(i)
|
||||
last = self._connections.pop(i)[0]
|
||||
count += 1
|
||||
_logger.info('%r: Closed %d connections %s', self, count,
|
||||
(dsn and last and 'to %r' % last.dsn) or '')
|
||||
|
||||
|
||||
class Connection(object):
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
__all__ = ['synchronized', 'lazy_property', 'conditional']
|
||||
__all__ = ['synchronized', 'lazy_property', 'classproperty', 'conditional']
|
||||
|
||||
from functools import wraps
|
||||
from inspect import getsourcefile
|
||||
@@ -116,4 +116,12 @@ def compose(a, b):
|
||||
return a(b(*args, **kwargs))
|
||||
return wrapper
|
||||
|
||||
|
||||
class _ClassProperty(property):
|
||||
def __get__(self, cls, owner):
|
||||
return self.fget.__get__(None, owner)()
|
||||
|
||||
def classproperty(func):
|
||||
return _ClassProperty(classmethod(func))
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -96,19 +96,21 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
|
||||
image.save(background_stream, filetype)
|
||||
return background_stream.getvalue().encode(encoding)
|
||||
|
||||
def image_resize_and_sharpen(image, size, factor=2.0):
|
||||
def image_resize_and_sharpen(image, size, preserve_aspect_ratio=False, factor=2.0):
|
||||
"""
|
||||
Create a thumbnail by resizing while keeping ratio.
|
||||
A sharpen filter is applied for a better looking result.
|
||||
|
||||
:param image: PIL.Image.Image()
|
||||
:param size: 2-tuple(width, height)
|
||||
:param preserve_aspect_ratio: boolean (default: False)
|
||||
:param factor: Sharpen factor (default: 2.0)
|
||||
"""
|
||||
if image.mode != 'RGBA':
|
||||
image = image.convert('RGBA')
|
||||
image.thumbnail(size, Image.ANTIALIAS)
|
||||
size = image.size
|
||||
if preserve_aspect_ratio:
|
||||
size = image.size
|
||||
sharpener = ImageEnhance.Sharpness(image)
|
||||
resized_image = sharpener.enhance(factor)
|
||||
# create a transparent image for background and paste the image on it
|
||||
|
||||
Reference in New Issue
Block a user