Master haresh 05072018

This commit is contained in:
Haresh Chavda 2018-07-13 09:26:56 +00:00 committed by Parthiv Patel
parent a6b79054ac
commit 90389d2065
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.payment_term_id = payment_term.id,
self.company_id = requisition.company_id.id self.company_id = requisition.company_id.id
self.currency_id = currency.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.origin = requisition.name
self.partner_ref = requisition.name # to control vendor bill based on agreement reference
self.notes = requisition.description self.notes = requisition.description
self.date_order = requisition.date_end or fields.Datetime.now() self.date_order = requisition.date_end or fields.Datetime.now()
self.picking_type_id = requisition.picking_type_id.id 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): 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) 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') ], 'Day of Week', required=True, index=True, default='0')
date_from = fields.Date(string='Starting Date') date_from = fields.Date(string='Starting Date')
date_to = fields.Date(string='End 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) hour_to = fields.Float(string='Work to', required=True)
calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade') 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) compute_leaves=True)
self.assertEqual(res, 33.0) 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): def test_calendar_timezone(self):
# user in timezone UTC-9 asks for work hours # 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) # 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'], 'depends': ['sales_team', 'account', 'portal'],
'data': [ 'data': [
'security/sale_security.xml',
'security/ir.model.access.csv',
'data/ir_sequence_data.xml', 'data/ir_sequence_data.xml',
'data/sale_data.xml', 'data/sale_data.xml',
'report/sale_report.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/sale_report_templates.xml',
'report/invoice_report_templates.xml', 'report/invoice_report_templates.xml',
'report/report_all_channels_sales_views.xml', 'report/report_all_channels_sales_views.xml',
'security/sale_security.xml',
'security/ir.model.access.csv',
'wizard/sale_make_invoice_advance_views.xml', 'wizard/sale_make_invoice_advance_views.xml',
'views/sale_views.xml', 'views/sale_views.xml',
'views/account_invoice_views.xml', 'views/account_invoice_views.xml',

View File

@ -5,7 +5,7 @@
<!--Email template --> <!--Email template -->
<record id="email_template_edi_sale" model="mail.template"> <record id="email_template_edi_sale" model="mail.template">
<field name="name">Sales Order - Send by Email</field> <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="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="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="sale.model_sale_order"/> <field name="model_id" ref="sale.model_sale_order"/>
@ -70,7 +70,7 @@ from ${object.company_id.name}.
<field name="body_html"><![CDATA[<html> <field name="body_html"><![CDATA[<html>
<head></head> <head></head>
% set record = ctx.get('record') % 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;"> <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"> <table border="0" width="100%" cellpadding="0" bgcolor="#ededed" style="padding: 20px; background-color: #ededed; border-collapse:separate;" summary="o_mail_notification">
<tbody> <tbody>

View File

@ -39,6 +39,9 @@ class AccountInvoice(models.Model):
def _onchange_delivery_address(self): def _onchange_delivery_address(self):
addr = self.partner_id.address_get(['delivery']) addr = self.partner_id.address_get(['delivery'])
self.partner_shipping_id = addr and addr.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 @api.multi
def action_invoice_paid(self): def action_invoice_paid(self):
@ -96,15 +99,6 @@ class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line' _inherit = 'account.invoice.line'
_order = 'invoice_id, layout_category_id, sequence, id' _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_line_ids = fields.Many2many(
'sale.order.line', 'sale.order.line',
'sale_order_line_invoice_rel', '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_id = fields.Many2one('sale.layout_category', string='Section')
layout_category_sequence = fields.Integer(string='Layout Sequence') layout_category_sequence = fields.Integer(string='Layout Sequence')
# TODO: remove layout_category_sequence in master or make it work properly # 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 return r
sales_count = fields.Integer(compute='_sales_count', string='# Sales') 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') @api.depends('product_variant_ids.sales_count')
def _sales_count(self): def _sales_count(self):
for product in 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 @api.multi
def action_view_sales(self): 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 # Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
# 'account.invoice.refund') # 'account.invoice.refund')
# use like as origin may contains multiple references (e.g. 'SO01, SO02') # 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(',')]) invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')])
# Search for refunds as well # Search for refunds as well
refund_ids = self.env['account.invoice'].browse() 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') precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
invoices = {} invoices = {}
references = {} references = {}
invoices_origin = {}
invoices_name = {}
for order in self: for order in self:
group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id) 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): 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) invoice = inv_obj.create(inv_data)
references[invoice] = order references[invoice] = order
invoices[group_key] = invoice invoices[group_key] = invoice
invoices_origin[group_key] = [invoice.origin]
invoices_name[group_key] = [invoice.name]
elif group_key in invoices: elif group_key in invoices:
vals = {} if order.name not in invoices_origin[group_key]:
if order.name not in invoices[group_key].origin.split(', '): invoices_origin[group_key].append(order.name)
vals['origin'] = invoices[group_key].origin + ', ' + order.name if order.client_order_ref and order.client_order_ref not in invoices_name[group_key]:
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: invoices_name[group_key].append(order.client_order_ref)
vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref
invoices[group_key].write(vals)
if line.qty_to_invoice > 0: if line.qty_to_invoice > 0:
line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice) line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
elif line.qty_to_invoice < 0 and final: elif line.qty_to_invoice < 0 and final:
@ -437,7 +441,11 @@ class SaleOrder(models.Model):
if references.get(invoices.get(group_key)): if references.get(invoices.get(group_key)):
if order not in references[invoices[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: if not invoices:
raise UserError(_('There is no invoiceable line.')) raise UserError(_('There is no invoiceable line.'))
@ -1084,23 +1092,21 @@ class SaleOrderLine(models.Model):
@api.multi @api.multi
def name_get(self): def name_get(self):
if self._context.get('sale_show_order_product_name'):
result = [] result = []
for so_line in self: 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)) result.append((so_line.id, name))
return result return result
return super(SaleOrderLine, self).name_get()
@api.model @api.model
def name_search(self, name='', args=None, operator='ilike', limit=100): 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'): if operator in ('ilike', 'like', '=', '=like', '=ilike'):
domain = expression.AND([ args = expression.AND([
args or [], args or [],
['|', ('order_id.name', operator, name), ('name', operator, name)] ['|', ('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) return super(SaleOrderLine, self).name_search(name, args, operator, limit)
@api.multi @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, 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. 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 @api.multi
def _analytic_compute_delivered_quantity(self): def _analytic_compute_delivered_quantity(self):
@ -1242,3 +1248,7 @@ class SaleOrderLine(models.Model):
so_line.write({'qty_delivered': qty}) so_line.write({'qty_delivered': qty})
return True 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 class="text-right">Unit Price</th>
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">Disc.(%)</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">Taxes</th>
<th class="text-right">Price</th> <th class="text-right">Amount</th>
</tr> </tr>
</thead> </thead>
<tbody class="invoice_tbody"> <tbody class="invoice_tbody">
@ -53,7 +53,7 @@
<td class="text-right"> <td class="text-right">
<span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/> <span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
</td> </td>
<td class="text-right" groups="sale.group_show_price_subtotal"> <td class="text-right">
<span t-field="l.price_subtotal" <span t-field="l.price_subtotal"
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/> t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td> </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()))) (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 u on (u.id=sol.product_uom)
LEFT JOIN product_uom u2 on (u2.id=pt.uom_id) LEFT JOIN product_uom u2 on (u2.id=pt.uom_id)
WHERE so.state != 'cancel'
""" % self.env['res.currency']._select_companies_rates() """ % self.env['res.currency']._select_companies_rates()
return so_str return so_str

View File

@ -4,7 +4,7 @@
<field name="name">report.all.channels.sales.pivot</field> <field name="name">report.all.channels.sales.pivot</field>
<field name="model">report.all.channels.sales</field> <field name="model">report.all.channels.sales</field>
<field name="arch" type="xml"> <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="name" type="row"/>
<field name="price_total" string="Total Price" type="measure"/> <field name="price_total" string="Total Price" type="measure"/>
</pivot> </pivot>

View File

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

View File

@ -60,6 +60,11 @@
<strong>Payment Terms:</strong> <strong>Payment Terms:</strong>
<p t-field="doc.payment_term_id"/> <p t-field="doc.payment_term_id"/>
</div> </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"> <div name="branch" t-if="doc.branch_id" class="col-xs-3" groups="base_branch_company.group_multi_branch">
<strong>Branch:</strong> <strong>Branch:</strong>
<p t-field="doc.branch_id"/> <p t-field="doc.branch_id"/>

View File

@ -158,9 +158,6 @@
<field name="portal_confirmation_options" class="o_light_label" widget="radio" <field name="portal_confirmation_options" class="o_light_label" widget="radio"
attrs="{'required': [('portal_confirmation', '=', True)]}"/> attrs="{'required': [('portal_confirmation', '=', True)]}"/>
</div> </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> </div>
</div> </div>
@ -269,7 +266,7 @@
</div> </div>
<h2>Invoicing</h2> <h2>Invoicing</h2>
<div class="row mt16 o_settings_container"> <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"> <div class="o_setting_right_pane">
<label for="default_invoice_policy"/> <label for="default_invoice_policy"/>
<div class="text-muted"> <div class="text-muted">

View File

@ -12,8 +12,6 @@
Create a Quotation, the first step of a new sale. Create a Quotation, the first step of a new sale.
</p><p> </p><p>
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment. 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> </p>
</field> </field>
</record> </record>

View File

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

View File

@ -586,9 +586,6 @@
</p><p> </p><p>
Your next actions should flow efficiently: confirm the Quotation Your next actions should flow efficiently: confirm the Quotation
to a Sales Order, then create the Invoice and collect the Payment. 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> </p>
</field> </field>
</record> </record>
@ -839,8 +836,6 @@
Create a Quotation, the first step of a new sale. Create a Quotation, the first step of a new sale.
</p><p> </p><p>
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment. 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> </p>
</field> </field>
</record> </record>
@ -880,9 +875,6 @@
</p><p> </p><p>
Your next actions should flow efficiently: confirm the Quotation Your next actions should flow efficiently: confirm the Quotation
to a Sales Order, then create the Invoice and collect the Payment. 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> </p>
</field> </field>
</record> </record>

View File

@ -65,7 +65,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
account_id = False account_id = False
if self.product_id.id: 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: if not account_id:
inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category') 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 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', 'invoice_policy': 'order',
'property_account_income_id': self.deposit_account_id.id, 'property_account_income_id': self.deposit_account_id.id,
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)], '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, in_modal: false,
run: function (actions) { run: function (actions) {
actions.auto(); 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) { if ($(".modal-footer .btn-primary").length) {
actions.auto(".modal-footer .btn-primary"); actions.auto(".modal-footer .btn-primary");
} }

View File

@ -1,20 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. # 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 models # noqa
from . import report # 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'], 'depends':['sale_management'],
'demo':['data/sale_margin_demo.xml'], 'demo':['data/sale_margin_demo.xml'],
'data':['security/ir.model.access.csv','views/sale_margin_view.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')]) 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) quantity = self.uom_id._compute_quantity(self.quantity, self.product_id.uom_id)
# Put moves in fixed order by date executed # Put moves in fixed order by date executed
moves = s_line.move_ids moves = s_line.move_ids.sorted(lambda x: x.date)
moves.sorted(lambda x: x.date)
# Go through all the moves and do nothing until you get to qty_done # 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 # Beyond qty_done we need to calculate the average of the price_unit
# on the moves we encounter. # on the moves we encounter.

View File

@ -33,7 +33,7 @@ class SaleOrder(models.Model):
for order in self: for order in self:
dates_list = [] dates_list = []
order_datetime = fields.Datetime.from_string(order.date_order) 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) dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
dates_list.append(dt) dates_list.append(dt)
if dates_list: if dates_list:

View File

@ -17,7 +17,6 @@ class PaymentPortal(http.Controller):
:return html: form containing all values related to the acquirer to :return html: form containing all values related to the acquirer to
redirect customers to the acquirer website """ redirect customers to the acquirer website """
success_url = kwargs.get('success_url', '/my') success_url = kwargs.get('success_url', '/my')
callback_method = kwargs.get('callback_method', '')
order_sudo = request.env['sale.order'].sudo().browse(order_id) order_sudo = request.env['sale.order'].sudo().browse(order_id)
if not order_sudo: if not order_sudo:
@ -28,17 +27,16 @@ class PaymentPortal(http.Controller):
except: except:
return False 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 token = request.env['payment.token'].sudo() # currently no support of payment tokens
tx = request.env['payment.transaction'].sudo()._check_or_create_sale_tx( tx = request.env['payment.transaction'].sudo()._check_or_create_sale_tx(
order_sudo, order_sudo,
acquirer, acquirer,
payment_token=token, payment_token=token,
tx_type='form_save' if save_token else 'form', 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 # set the transaction id into the session
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id 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 """ """ Use a token to perform a s2s transaction """
error_url = kwargs.get('error_url', '/my') error_url = kwargs.get('error_url', '/my')
success_url = kwargs.get('success_url', '/my') success_url = kwargs.get('success_url', '/my')
callback_method = kwargs.get('callback_method', '')
access_token = kwargs.get('access_token') access_token = kwargs.get('access_token')
params = {} params = {}
if access_token: if access_token:
@ -73,7 +70,9 @@ class PaymentPortal(http.Controller):
token = request.env['payment.token'].sudo().browse(int(pm_id)) token = request.env['payment.token'].sudo().browse(int(pm_id))
except (ValueError, TypeError): except (ValueError, TypeError):
token = False 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' params['error'] = 'pay_sale_invalid_token'
return request.redirect(_build_url_w_params(error_url, params)) return request.redirect(_build_url_w_params(error_url, params))
@ -83,11 +82,7 @@ class PaymentPortal(http.Controller):
token.acquirer_id, token.acquirer_id,
payment_token=token, payment_token=token,
tx_type='server2server', 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 # set the transaction id into the session
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id 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): def _order_get_page_view_values(self, order, access_token, **kwargs):
values = super(CustomerPortal, self)._order_get_page_view_values(order, access_token, **kwargs) values = super(CustomerPortal, self)._order_get_page_view_values(order, access_token, **kwargs)
if values['portal_confirmation'] == 'pay': 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 return values

View File

@ -3,6 +3,7 @@
import logging import logging
from flectra import api, fields, models, _ from flectra import api, fields, models, _
from flectra.exceptions import UserError
from flectra.tools import float_compare from flectra.tools import float_compare
_logger = logging.getLogger(__name__) _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) _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'): if self.sale_order_id.state in ('draft', 'sent'):
self.sale_order_id.with_context(send_email=True).action_confirm() 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': 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) _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() self.sale_order_id.force_quotation_send()
@ -85,8 +85,16 @@ class PaymentTransaction(models.Model):
# force_company needed for company_dependent fields # force_company needed for company_dependent fields
ctx_company = {'company_id': self.sale_order_id.company_id.id, ctx_company = {'company_id': self.sale_order_id.company_id.id,
'force_company': 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.sale_order_id.with_context(**ctx_company).action_invoice_create()
created_invoice = self.env['account.invoice'].browse(created_invoice).with_context(**ctx_company) 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: if created_invoice:
_logger.info('<%s> transaction completed, auto-generated invoice %s (ID %s) for %s (ID %s)', _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, 'currency_id': order.pricelist_id.currency_id.id,
'partner_id': order.partner_id.id, 'partner_id': order.partner_id.id,
'partner_country_id': order.partner_id.country_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, 'sale_order_id': order.id,
} }
if add_tx_values: if add_tx_values:

View File

@ -67,6 +67,11 @@
</t> </t>
</div> </div>
</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> </div>
</xpath> </xpath>
<xpath expr="//t[@t-if='invoices']" position="before"> <xpath expr="//t[@t-if='invoices']" position="before">

View File

@ -1,5 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, SUPERUSER_ID
from . import models from . import models
from . import controllers 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', 'data/sale_service_demo.xml',
], ],
'auto_install': True, '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'][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) dashboard_values['hours']['total'] += float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
# rates # rates
dashboard_values['rates'][billable_type] = 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'] += round(data.get('unit_amount') / dashboard_total_hours * 100, 2) dashboard_values['rates']['total'] += dashboard_total_hours and round(data.get('unit_amount') / dashboard_total_hours * 100, 2) or 0
# money_amount # money_amount
so_lines = values['timesheet_lines'].mapped('so_line') 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 line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol
total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue
else: 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 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) 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()]) 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 @api.model
def create(self, values): def create(self, values):
# sub task has the same so line than their parent # sub task has the same so line than their parent
if 'parent_id' in values: parent_id = values['parent_id'] if 'parent_id' in values else self.env.context.get('default_parent_id')
values['sale_line_id'] = self.env['project.task'].browse(values['parent_id']).sudo().sale_line_id.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) return super(ProjectTask, self).create(values)
@api.multi @api.multi

View File

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

View File

@ -17,6 +17,11 @@ class TestSaleTimesheet(TestSale):
# after the 6 june of current year. # after the 6 june of current year.
self.env.ref('base.rateUSDbis').unlink() 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 # create project
self.project = self.env['project.project'].create({ self.project = self.env['project.project'].create({
'name': 'Project for my timesheets', 'name': 'Project for my timesheets',

View File

@ -29,7 +29,7 @@
string="Sales Order"/> string="Sales Order"/>
</xpath> </xpath>
<field name="user_id" position="after"> <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>
</field> </field>
</record> </record>