Merge branch 'master-haresh-05072018' into 'master-patch-july-2018'

Master haresh 05072018

See merge request flectra-hq/flectra!93
This commit is contained in:
Parthiv Patel 2018-07-13 09:26:56 +00:00
commit 548b771a48
36 changed files with 152 additions and 103 deletions

View File

@ -203,8 +203,12 @@ class PurchaseOrder(models.Model):
self.payment_term_id = payment_term.id,
self.company_id = requisition.company_id.id
self.currency_id = currency.id
if not self.origin or requisition.name not in self.origin.split(', '):
if self.origin:
if requisition.name:
self.origin = self.origin + ', ' + requisition.name
else:
self.origin = requisition.name
self.partner_ref = requisition.name # to control vendor bill based on agreement reference
self.notes = requisition.description
self.date_order = requisition.date_end or fields.Datetime.now()
self.picking_type_id = requisition.picking_type_id.id

View File

@ -18,6 +18,8 @@ from flectra.tools.float_utils import float_compare
def float_to_time(float_hour):
if float_hour == 24.0:
return datetime.time.max
return datetime.time(int(math.modf(float_hour)[1]), int(60 * math.modf(float_hour)[0]), 0)
@ -643,7 +645,9 @@ class ResourceCalendarAttendance(models.Model):
], 'Day of Week', required=True, index=True, default='0')
date_from = fields.Date(string='Starting Date')
date_to = fields.Date(string='End Date')
hour_from = fields.Float(string='Work from', required=True, index=True, help="Start and End time of working.")
hour_from = fields.Float(string='Work from', required=True, index=True,
help="Start and End time of working.\n"
"A specific value of 24:00 is interpreted as 23:59:59.999999.")
hour_to = fields.Float(string='Work to', required=True)
calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade')

View File

@ -243,6 +243,21 @@ class ResourceWorkingHours(TestResourceCommon):
compute_leaves=True)
self.assertEqual(res, 33.0)
def test_calendar_working_hours_24(self):
self.att_4 = self.env['resource.calendar.attendance'].create({
'name': 'Att4',
'calendar_id': self.calendar.id,
'dayofweek': '2',
'hour_from': 0,
'hour_to': 24
})
res = self.calendar.get_work_hours_count(
Datetime.from_string('2018-06-19 23:00:00'),
Datetime.from_string('2018-06-21 01:00:00'),
self.resource1_id,
compute_leaves=True)
self.assertAlmostEqual(res, 24.0)
def test_calendar_timezone(self):
# user in timezone UTC-9 asks for work hours
# Limits: between 2013-02-19 10:00:00 and 2013-02-26 15:30:00 (User TZ)

View File

@ -12,6 +12,8 @@ This module contains all the common features of Sales Management and eCommerce.
""",
'depends': ['sales_team', 'account', 'portal'],
'data': [
'security/sale_security.xml',
'security/ir.model.access.csv',
'data/ir_sequence_data.xml',
'data/sale_data.xml',
'report/sale_report.xml',
@ -20,8 +22,6 @@ This module contains all the common features of Sales Management and eCommerce.
'report/sale_report_templates.xml',
'report/invoice_report_templates.xml',
'report/report_all_channels_sales_views.xml',
'security/sale_security.xml',
'security/ir.model.access.csv',
'wizard/sale_make_invoice_advance_views.xml',
'views/sale_views.xml',
'views/account_invoice_views.xml',

View File

@ -5,7 +5,7 @@
<!--Email template -->
<record id="email_template_edi_sale" model="mail.template">
<field name="name">Sales Order - Send by Email</field>
<field name="email_from">${(object.user_id.email and '%s &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
<field name="email_from">${(object.user_id.email and '&quot;%s&quot; &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="sale.model_sale_order"/>
@ -70,7 +70,7 @@ from ${object.company_id.name}.
<field name="body_html"><![CDATA[<html>
<head></head>
% set record = ctx.get('record')
% set company = record and record.company_id or user.company_id
% set company = record and record.company_id or ctx.get('company')
<body style="margin: 0; padding: 0;">
<table border="0" width="100%" cellpadding="0" bgcolor="#ededed" style="padding: 20px; background-color: #ededed; border-collapse:separate;" summary="o_mail_notification">
<tbody>

View File

@ -39,6 +39,9 @@ class AccountInvoice(models.Model):
def _onchange_delivery_address(self):
addr = self.partner_id.address_get(['delivery'])
self.partner_shipping_id = addr and addr.get('delivery')
if self.env.context.get('type', 'out_invoice') == 'out_invoice':
company = self.company_id or self.env.user.company_id
self.comment = company.with_context(lang=self.partner_id.lang).sale_note
@api.multi
def action_invoice_paid(self):
@ -96,15 +99,6 @@ class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
_order = 'invoice_id, layout_category_id, sequence, id'
@api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity',
'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id',
'invoice_id.date_invoice')
def _compute_total_price(self):
for line in self:
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.invoice_line_tax_ids.compute_all(price, line.invoice_id.currency_id, line.quantity, product=line.product_id, partner=line.invoice_id.partner_id)
line.price_total = taxes['total_included']
sale_line_ids = fields.Many2many(
'sale.order.line',
'sale_order_line_invoice_rel',
@ -113,4 +107,3 @@ class AccountInvoiceLine(models.Model):
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
layout_category_sequence = fields.Integer(string='Layout Sequence')
# TODO: remove layout_category_sequence in master or make it work properly
price_total = fields.Monetary(compute='_compute_total_price', string='Total Amount', store=True)

View File

@ -23,3 +23,6 @@ class ProductProduct(models.Model):
return r
sales_count = fields.Integer(compute='_sales_count', string='# Sales')
def _get_invoice_policy(self):
return self.invoice_policy

View File

@ -24,7 +24,7 @@ class ProductTemplate(models.Model):
@api.depends('product_variant_ids.sales_count')
def _sales_count(self):
for product in self:
product.sales_count = sum([p.sales_count for p in product.product_variant_ids])
product.sales_count = sum([p.sales_count for p in product.with_context(active_test=False).product_variant_ids])
@api.multi
def action_view_sales(self):

View File

@ -59,7 +59,7 @@ class SaleOrder(models.Model):
# Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
# 'account.invoice.refund')
# use like as origin may contains multiple references (e.g. 'SO01, SO02')
refunds = invoice_ids.search([('origin', 'like', order.name)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id), ('branch_id', '=', order.branch_id.id)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')])
# Search for refunds as well
refund_ids = self.env['account.invoice'].browse()
@ -413,6 +413,9 @@ class SaleOrder(models.Model):
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
invoices = {}
references = {}
invoices_origin = {}
invoices_name = {}
for order in self:
group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id)
for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0):
@ -423,13 +426,14 @@ class SaleOrder(models.Model):
invoice = inv_obj.create(inv_data)
references[invoice] = order
invoices[group_key] = invoice
invoices_origin[group_key] = [invoice.origin]
invoices_name[group_key] = [invoice.name]
elif group_key in invoices:
vals = {}
if order.name not in invoices[group_key].origin.split(', '):
vals['origin'] = invoices[group_key].origin + ', ' + order.name
if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', ') and order.client_order_ref != invoices[group_key].name:
vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref
invoices[group_key].write(vals)
if order.name not in invoices_origin[group_key]:
invoices_origin[group_key].append(order.name)
if order.client_order_ref and order.client_order_ref not in invoices_name[group_key]:
invoices_name[group_key].append(order.client_order_ref)
if line.qty_to_invoice > 0:
line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
elif line.qty_to_invoice < 0 and final:
@ -437,7 +441,11 @@ class SaleOrder(models.Model):
if references.get(invoices.get(group_key)):
if order not in references[invoices[group_key]]:
references[invoice] = references[invoice] | order
references[invoices[group_key]] |= order
for group_key in invoices:
invoices[group_key].write({'name': ', '.join(invoices_name[group_key]),
'origin': ', '.join(invoices_origin[group_key])})
if not invoices:
raise UserError(_('There is no invoiceable line.'))
@ -1084,23 +1092,21 @@ class SaleOrderLine(models.Model):
@api.multi
def name_get(self):
if self._context.get('sale_show_order_product_name'):
result = []
for so_line in self:
name = '%s - %s' % (so_line.order_id.name, so_line.product_id.name)
name = '%s - %s' % (so_line.order_id.name, so_line.name.split('\n')[0] or so_line.product_id.name)
if so_line.order_partner_id.ref:
name = '%s (%s)' % (name, so_line.order_partner_id.ref)
result.append((so_line.id, name))
return result
return super(SaleOrderLine, self).name_get()
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
if self._context.get('sale_show_order_product_name'):
if operator in ('ilike', 'like', '=', '=like', '=ilike'):
domain = expression.AND([
args = expression.AND([
args or [],
['|', ('order_id.name', operator, name), ('name', operator, name)]
])
return self.search(domain, limit=limit).name_get()
return super(SaleOrderLine, self).name_search(name, args, operator, limit)
@api.multi
@ -1202,7 +1208,7 @@ class SaleOrderLine(models.Model):
on SO lines. This method is a hook: since analytic line are used for timesheet,
expense, ... each use case should provide its part of the domain.
"""
return [('so_line', 'in', self.ids), ('amount', '<=', 0.0)]
return ['&', ('so_line', 'in', self.ids), ('amount', '<=', 0.0)]
@api.multi
def _analytic_compute_delivered_quantity(self):
@ -1242,3 +1248,7 @@ class SaleOrderLine(models.Model):
so_line.write({'qty_delivered': qty})
return True
def _is_delivery(self):
self.ensure_one()
return False

View File

@ -22,7 +22,7 @@
<th class="text-right">Unit Price</th>
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">Disc.(%)</th>
<th class="text-right">Taxes</th>
<th class="text-right">Price</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody class="invoice_tbody">
@ -53,7 +53,7 @@
<td class="text-right">
<span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
</td>
<td class="text-right" groups="sale.group_show_price_subtotal">
<td class="text-right">
<span t-field="l.price_subtotal"
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>

View File

@ -57,6 +57,7 @@ class PosSaleReport(models.Model):
(cr.date_end IS NULL OR cr.date_end > COALESCE(so.date_order, now())))
LEFT JOIN product_uom u on (u.id=sol.product_uom)
LEFT JOIN product_uom u2 on (u2.id=pt.uom_id)
WHERE so.state != 'cancel'
""" % self.env['res.currency']._select_companies_rates()
return so_str

View File

@ -4,7 +4,7 @@
<field name="name">report.all.channels.sales.pivot</field>
<field name="model">report.all.channels.sales</field>
<field name="arch" type="xml">
<pivot string="All Channels Sales Orders Analysis">
<pivot string="All Channels Sales Orders Analysis" disable_linking="True">
<field name="name" type="row"/>
<field name="price_total" string="Total Price" type="measure"/>
</pivot>

View File

@ -16,6 +16,7 @@
string="PRO-FORMA Invoice"
model="sale.order"
report_type="qweb-pdf"
groups="sale.group_proforma_sales"
file="sale.report_saleorder_pro_forma"
name="sale.report_saleorder_pro_forma"
print_report_name="'PRO-FORMA - %s' % (object.name)"

View File

@ -60,6 +60,11 @@
<strong>Payment Terms:</strong>
<p t-field="doc.payment_term_id"/>
</div>
<div t-if="doc.validity_date and doc.state in ['draft', 'sent']" class="col-xs-3">
<strong>Expiration Date:</strong>
<p t-field="doc.validity_date"/>
</div>
<div name="branch" t-if="doc.branch_id" class="col-xs-3" groups="base_branch_company.group_multi_branch">
<strong>Branch:</strong>
<p t-field="doc.branch_id"/>

View File

@ -158,9 +158,6 @@
<field name="portal_confirmation_options" class="o_light_label" widget="radio"
attrs="{'required': [('portal_confirmation', '=', True)]}"/>
</div>
<div attrs="{'invisible': [('portal_confirmation_options', '!=', 'pay')]}">
<button name='%(payment.action_payment_acquirer)d' icon="fa-arrow-right" type="action" string="Payment Acquirers" class="btn-link"/>
</div>
</div>
</div>
</div>
@ -269,7 +266,7 @@
</div>
<h2>Invoicing</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box" title="This default value is applied to any new product created. This can be changed in the product detail form.">
<div class="col-xs-12 col-md-6 o_setting_box hidden" title="This default value is applied to any new product created. This can be changed in the product detail form.">
<div class="o_setting_right_pane">
<label for="default_invoice_policy"/>
<div class="text-muted">

View File

@ -12,8 +12,6 @@
Create a Quotation, the first step of a new sale.
</p><p>
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment.
</p><p>
Note that once a Quotation becomes a Sales Order, it will be moved from the Quotations list to the Sales Order list.
</p>
</field>
</record>

View File

@ -148,7 +148,7 @@
<span class="label label-default"><i class="fa fa-fw fa-remove"/> Cancelled</span>
</t>
<t t-if="order.state == 'done'">
<span class="label label-default"><i class="fa fa-fw fa-remove"/> Done</span>
<span class="label label-success"><i class="fa fa-fw fa-check" /> Done</span>
</t>
</h4>
</div>

View File

@ -586,9 +586,6 @@
</p><p>
Your next actions should flow efficiently: confirm the Quotation
to a Sales Order, then create the Invoice and collect the Payment.
</p><p>
Note that once a Quotation becomes a Sales Order, it will be moved
from the Quotations list to the Sales Order list.
</p>
</field>
</record>
@ -839,8 +836,6 @@
Create a Quotation, the first step of a new sale.
</p><p>
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment.
</p><p>
Note that once a Quotation becomes a Sales Order, it will be moved from the Quotations list to the Sales Order list.
</p>
</field>
</record>
@ -880,9 +875,6 @@
</p><p>
Your next actions should flow efficiently: confirm the Quotation
to a Sales Order, then create the Invoice and collect the Payment.
</p><p>
Note that once a Quotation becomes a Sales Order, it will be moved
from the Quotations list to the Sales Order list.
</p>
</field>
</record>

View File

@ -65,7 +65,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
account_id = False
if self.product_id.id:
account_id = self.product_id.property_account_income_id.id
account_id = self.product_id.property_account_income_id.id or self.product_id.categ_id.property_account_income_categ_id.id
if not account_id:
inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category')
account_id = order.fiscal_position_id.map_account(inc_acc).id if inc_acc else False
@ -179,4 +179,5 @@ class SaleAdvancePaymentInv(models.TransientModel):
'invoice_policy': 'order',
'property_account_income_id': self.deposit_account_id.id,
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
'company_id': False,
}

View File

@ -44,6 +44,8 @@ tour.register('sale_tour', {
in_modal: false,
run: function (actions) {
actions.auto();
// There might be a modal because of the view:
// sale.order.form.editable.list, enabled by some groups
if ($(".modal-footer .btn-primary").length) {
actions.auto(".modal-footer .btn-primary");
}

View File

@ -1,20 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from functools import partial
import flectra
from flectra import api, SUPERUSER_ID
from . import models # noqa
from . import report # noqa
def uninstall_hook(cr, registry):
def recreate_view(dbname):
db_registry = openerp.modules.registry.Registry.new(dbname)
with api.Environment.manage(), db_registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
if 'sale.report' in env:
env['sale.report'].init()
cr.after("commit", partial(recreate_view, cr.dbname))

View File

@ -16,5 +16,4 @@ Price and Cost Price.
'depends':['sale_management'],
'demo':['data/sale_margin_demo.xml'],
'data':['security/ir.model.access.csv','views/sale_margin_view.xml'],
'uninstall_hook': "uninstall_hook",
}

View File

@ -62,8 +62,7 @@ class AccountInvoiceLine(models.Model):
qty_done = sum([x.uom_id._compute_quantity(x.quantity, x.product_id.uom_id) for x in s_line.invoice_lines if x.invoice_id.state in ('open', 'paid')])
quantity = self.uom_id._compute_quantity(self.quantity, self.product_id.uom_id)
# Put moves in fixed order by date executed
moves = s_line.move_ids
moves.sorted(lambda x: x.date)
moves = s_line.move_ids.sorted(lambda x: x.date)
# Go through all the moves and do nothing until you get to qty_done
# Beyond qty_done we need to calculate the average of the price_unit
# on the moves we encounter.

View File

@ -33,7 +33,7 @@ class SaleOrder(models.Model):
for order in self:
dates_list = []
order_datetime = fields.Datetime.from_string(order.date_order)
for line in order.order_line.filtered(lambda x: x.state != 'cancel'):
for line in order.order_line.filtered(lambda x: x.state != 'cancel' and not x._is_delivery()):
dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
dates_list.append(dt)
if dates_list:

View File

@ -17,7 +17,6 @@ class PaymentPortal(http.Controller):
:return html: form containing all values related to the acquirer to
redirect customers to the acquirer website """
success_url = kwargs.get('success_url', '/my')
callback_method = kwargs.get('callback_method', '')
order_sudo = request.env['sale.order'].sudo().browse(order_id)
if not order_sudo:
@ -28,17 +27,16 @@ class PaymentPortal(http.Controller):
except:
return False
if request.env.user == request.env.ref('base.public_user'):
save_token = False
token = request.env['payment.token'].sudo() # currently no support of payment tokens
tx = request.env['payment.transaction'].sudo()._check_or_create_sale_tx(
order_sudo,
acquirer,
payment_token=token,
tx_type='form_save' if save_token else 'form',
add_tx_values={
'callback_model_id': request.env['ir.model'].sudo().search([('model', '=', order_sudo._name)], limit=1).id,
'callback_res_id': order_sudo.id,
'callback_method': callback_method,
})
)
# set the transaction id into the session
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id
@ -58,7 +56,6 @@ class PaymentPortal(http.Controller):
""" Use a token to perform a s2s transaction """
error_url = kwargs.get('error_url', '/my')
success_url = kwargs.get('success_url', '/my')
callback_method = kwargs.get('callback_method', '')
access_token = kwargs.get('access_token')
params = {}
if access_token:
@ -73,7 +70,9 @@ class PaymentPortal(http.Controller):
token = request.env['payment.token'].sudo().browse(int(pm_id))
except (ValueError, TypeError):
token = False
if not token:
token_owner = order_sudo.partner_id if request.env.user == request.env.ref('base.public_user') else request.env.user.partner_id
if not token or token.partner_id != token_owner:
params['error'] = 'pay_sale_invalid_token'
return request.redirect(_build_url_w_params(error_url, params))
@ -83,11 +82,7 @@ class PaymentPortal(http.Controller):
token.acquirer_id,
payment_token=token,
tx_type='server2server',
add_tx_values={
'callback_model_id': request.env['ir.model'].sudo().search([('model', '=', order_sudo._name)], limit=1).id,
'callback_res_id': order_sudo.id,
'callback_method': callback_method,
})
)
# set the transaction id into the session
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id

View File

@ -10,5 +10,16 @@ class CustomerPortal(CustomerPortal):
def _order_get_page_view_values(self, order, access_token, **kwargs):
values = super(CustomerPortal, self)._order_get_page_view_values(order, access_token, **kwargs)
if values['portal_confirmation'] == 'pay':
values.update(request.env['payment.acquirer']._get_available_payment_input(order.partner_id, order.company_id))
payment_inputs = request.env['payment.acquirer']._get_available_payment_input(order.partner_id, order.company_id)
# if not connected (using public user), the method _get_available_payment_input will return public user tokens
is_public_user = request.env.ref('base.public_user') == request.env.user
if is_public_user:
# we should not display payment tokens owned by the public user
payment_inputs.pop('pms', None)
token_count = request.env['payment.token'].sudo().search_count([('acquirer_id.company_id', '=', order.company_id.id),
('partner_id', '=', order.partner_id.id),
])
values['existing_token'] = token_count > 0
values.update(payment_inputs)
values['partner_id'] = order.partner_id if is_public_user else request.env.user.partner_id
return values

View File

@ -3,6 +3,7 @@
import logging
from flectra import api, fields, models, _
from flectra.exceptions import UserError
from flectra.tools import float_compare
_logger = logging.getLogger(__name__)
@ -68,7 +69,6 @@ class PaymentTransaction(models.Model):
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
if self.sale_order_id.state in ('draft', 'sent'):
self.sale_order_id.with_context(send_email=True).action_confirm()
self._generate_and_pay_invoice()
elif self.state not in ['cancel', 'error'] and self.sale_order_id.state == 'draft':
_logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
self.sale_order_id.force_quotation_send()
@ -85,8 +85,16 @@ class PaymentTransaction(models.Model):
# force_company needed for company_dependent fields
ctx_company = {'company_id': self.sale_order_id.company_id.id,
'force_company': self.sale_order_id.company_id.id}
# We might fail to create the invoice because there is no invoiceable lines. This will
# raise a UserError and break the workflow. Better catch the error.
try:
created_invoice = self.sale_order_id.with_context(**ctx_company).action_invoice_create()
created_invoice = self.env['account.invoice'].browse(created_invoice).with_context(**ctx_company)
except UserError:
_logger.warning('<%s> transaction completed, could not auto-generate invoice for %s (ID %s)',
self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id, exc_info=True)
return
if created_invoice:
_logger.info('<%s> transaction completed, auto-generated invoice %s (ID %s) for %s (ID %s)',
@ -174,7 +182,7 @@ class PaymentTransaction(models.Model):
'currency_id': order.pricelist_id.currency_id.id,
'partner_id': order.partner_id.id,
'partner_country_id': order.partner_id.country_id.id,
'reference': self.get_next_reference(order.name),
'reference': self._get_next_reference(order.name, acquirer=acquirer),
'sale_order_id': order.id,
}
if add_tx_values:

View File

@ -67,6 +67,11 @@
</t>
</div>
</div>
<div class="panel-body" t-if="existing_token">
<div class="col-md-offset-3 col-md-6">
<i class="fa fa-info"></i> You have credits card registered, you can log-in to be able to use them.
</div>
</div>
</div>
</xpath>
<xpath expr="//t[@t-if='invoices']" position="before">

View File

@ -1,5 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, SUPERUSER_ID
from . import models
from . import controllers
def uninstall_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
env['product.template'].search([
('service_type', '=', 'timesheet')
]).write({'service_type': 'manual'})
env['product.product'].search([
('service_type', '=', 'timesheet')
]).write({'service_type': 'manual'})

View File

@ -31,4 +31,5 @@ have real delivered quantities in sales orders.
'data/sale_service_demo.xml',
],
'auto_install': True,
'uninstall_hook': 'uninstall_hook',
}

View File

@ -56,8 +56,8 @@ class SaleTimesheetController(http.Controller):
dashboard_values['hours'][billable_type] = float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
dashboard_values['hours']['total'] += float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
# rates
dashboard_values['rates'][billable_type] = round(data.get('unit_amount') / dashboard_total_hours * 100, 2)
dashboard_values['rates']['total'] += round(data.get('unit_amount') / dashboard_total_hours * 100, 2)
dashboard_values['rates'][billable_type] = dashboard_total_hours and round(data.get('unit_amount') / dashboard_total_hours * 100, 2) or 0
dashboard_values['rates']['total'] += dashboard_total_hours and round(data.get('unit_amount') / dashboard_total_hours * 100, 2) or 0
# money_amount
so_lines = values['timesheet_lines'].mapped('so_line')

View File

@ -99,7 +99,8 @@ class AccountInvoice(models.Model):
line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol
total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue
else:
total_revenue_per_currency[timesheet_line.company_currency_id.id] += timesheet_line.timesheet_revenue
line_revenue = timesheet_line.timesheet_revenue
total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue
else: # last line: add the difference to avoid rounding problem
last_price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id)
total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.company_currency_id) for currency_id, amount in total_revenue_per_currency.items()])

View File

@ -70,8 +70,9 @@ class ProjectTask(models.Model):
@api.model
def create(self, values):
# sub task has the same so line than their parent
if 'parent_id' in values:
values['sale_line_id'] = self.env['project.task'].browse(values['parent_id']).sudo().sale_line_id.id
parent_id = values['parent_id'] if 'parent_id' in values else self.env.context.get('default_parent_id')
if parent_id:
values['sale_line_id'] = self.env['project.task'].browse(parent_id).sudo().sale_line_id.id
return super(ProjectTask, self).create(values)
@api.multi

View File

@ -175,6 +175,7 @@ class SaleOrderLine(models.Model):
@api.multi
def _analytic_compute_delivered_quantity_domain(self):
domain = super(SaleOrderLine, self)._analytic_compute_delivered_quantity_domain()
domain = expression.AND([domain, [('project_id', '=', False)]])
timesheet_domain = self._timesheet_compute_delivered_quantity_domain()
return expression.OR([domain, timesheet_domain])

View File

@ -17,6 +17,11 @@ class TestSaleTimesheet(TestSale):
# after the 6 june of current year.
self.env.ref('base.rateUSDbis').unlink()
# Make sure the company is in USD
self.env.cr.execute(
"UPDATE res_company SET currency_id = %s WHERE id = %s",
[self.env.ref('base.USD').id, self.env.user.company_id.id])
# create project
self.project = self.env['project.project'].create({
'name': 'Project for my timesheets',

View File

@ -29,7 +29,7 @@
string="Sales Order"/>
</xpath>
<field name="user_id" position="after">
<field name="sale_line_id" string="Sales Order Item" attrs="{'invisible': [('partner_id', '=', False)], 'readonly': [('parent_id', '!=', False)]}" options='{"no_open": True, "no_create": True}' context="{'sale_show_order_product_name': True}"/>
<field name="sale_line_id" string="Sales Order Item" attrs="{'invisible': [('partner_id', '=', False)], 'readonly': [('parent_id', '!=', False)]}" options='{"no_open": True, "no_create": True}'/>
</field>
</field>
</record>