Master bhumika 06072018
This commit is contained in:
parent
823c7409a5
commit
8e2d35fd5c
@ -33,7 +33,7 @@ class DeliveryCarrier(models.Model):
|
|||||||
# Internals for shipping providers #
|
# Internals for shipping providers #
|
||||||
# -------------------------------- #
|
# -------------------------------- #
|
||||||
|
|
||||||
name = fields.Char(required=True)
|
name = fields.Char(required=True, translate=True)
|
||||||
active = fields.Boolean(default=True)
|
active = fields.Boolean(default=True)
|
||||||
sequence = fields.Integer(help="Determine the display order", default=10)
|
sequence = fields.Integer(help="Determine the display order", default=10)
|
||||||
# This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex')
|
# This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex')
|
||||||
|
@ -91,7 +91,7 @@ class SaleOrder(models.Model):
|
|||||||
# Create the sales order line
|
# Create the sales order line
|
||||||
values = {
|
values = {
|
||||||
'order_id': self.id,
|
'order_id': self.id,
|
||||||
'name': carrier.name,
|
'name': carrier.with_context(lang=self.partner_id.lang).name,
|
||||||
'product_uom_qty': 1,
|
'product_uom_qty': 1,
|
||||||
'product_uom': carrier.product_id.uom_id.id,
|
'product_uom': carrier.product_id.uom_id.id,
|
||||||
'product_id': carrier.product_id.id,
|
'product_id': carrier.product_id.id,
|
||||||
@ -117,3 +117,7 @@ class SaleOrderLine(models.Model):
|
|||||||
if not line.product_id or not line.product_uom or not line.product_uom_qty:
|
if not line.product_id or not line.product_uom or not line.product_uom_qty:
|
||||||
return 0.0
|
return 0.0
|
||||||
line.product_qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id)
|
line.product_qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id)
|
||||||
|
|
||||||
|
def _is_delivery(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return self.is_delivery
|
||||||
|
@ -161,8 +161,11 @@ class StockPicking(models.Model):
|
|||||||
def send_to_shipper(self):
|
def send_to_shipper(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = self.carrier_id.send_shipping(self)[0]
|
res = self.carrier_id.send_shipping(self)[0]
|
||||||
|
if self.carrier_id.free_over and self.sale_id and self.sale_id._compute_amount_total_without_delivery() >= self.carrier_id.amount:
|
||||||
|
res['exact_price'] = 0.0
|
||||||
self.carrier_price = res['exact_price']
|
self.carrier_price = res['exact_price']
|
||||||
self.carrier_tracking_ref = res['tracking_number']
|
if res['tracking_number']:
|
||||||
|
self.carrier_tracking_ref = res['tracking_number']
|
||||||
order_currency = self.sale_id.currency_id or self.company_id.currency_id
|
order_currency = self.sale_id.currency_id or self.company_id.currency_id
|
||||||
msg = _("Shipment sent to carrier %s for shipping with tracking number %s<br/>Cost: %.2f %s") % (self.carrier_id.name, self.carrier_tracking_ref, self.carrier_price, order_currency.name)
|
msg = _("Shipment sent to carrier %s for shipping with tracking number %s<br/>Cost: %.2f %s") % (self.carrier_id.name, self.carrier_tracking_ref, self.carrier_price, order_currency.name)
|
||||||
self.message_post(body=msg)
|
self.message_post(body=msg)
|
||||||
|
@ -31,7 +31,10 @@ class TestDeliveryCost(common.TransactionCase):
|
|||||||
self.free_delivery = self.env.ref('delivery.free_delivery_carrier')
|
self.free_delivery = self.env.ref('delivery.free_delivery_carrier')
|
||||||
# as the tests hereunder assume all the prices in USD, we must ensure
|
# as the tests hereunder assume all the prices in USD, we must ensure
|
||||||
# that the company actually uses USD
|
# that the company actually uses USD
|
||||||
self.env.user.company_id.write({'currency_id': self.env.ref('base.USD').id})
|
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])
|
||||||
|
self.pricelist.currency_id = self.env.ref('base.USD').id
|
||||||
|
|
||||||
def test_00_delivery_cost(self):
|
def test_00_delivery_cost(self):
|
||||||
# In order to test Carrier Cost
|
# In order to test Carrier Cost
|
||||||
|
@ -438,9 +438,14 @@ class EventRegistration(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def message_get_suggested_recipients(self):
|
def message_get_suggested_recipients(self):
|
||||||
recipients = super(EventRegistration, self).message_get_suggested_recipients()
|
recipients = super(EventRegistration, self).message_get_suggested_recipients()
|
||||||
|
public_users = self.env['res.users'].sudo()
|
||||||
|
public_groups = self.env.ref("base.group_public", raise_if_not_found=False)
|
||||||
|
if public_groups:
|
||||||
|
public_users = public_groups.sudo().with_context(active_test=False).mapped("users")
|
||||||
try:
|
try:
|
||||||
for attendee in self:
|
for attendee in self:
|
||||||
if attendee.partner_id:
|
is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False
|
||||||
|
if attendee.partner_id and not is_public:
|
||||||
attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer'))
|
attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer'))
|
||||||
elif attendee.email:
|
elif attendee.email:
|
||||||
attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email'))
|
attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email'))
|
||||||
|
@ -4,7 +4,12 @@ from datetime import datetime
|
|||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from flectra import api, fields, models, tools
|
from flectra import api, fields, models, tools
|
||||||
|
from flectra.tools import exception_to_unicode
|
||||||
|
from flectra.tools.translate import _
|
||||||
|
|
||||||
|
import random
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_INTERVALS = {
|
_INTERVALS = {
|
||||||
'hours': lambda interval: relativedelta(hours=interval),
|
'hours': lambda interval: relativedelta(hours=interval),
|
||||||
@ -114,13 +119,52 @@ class EventMailScheduler(models.Model):
|
|||||||
self.write({'mail_sent': True})
|
self.write({'mail_sent': True})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _warn_template_error(self, scheduler, exception):
|
||||||
|
# We warn ~ once by hour ~ instead of every 10 min if the interval unit is more than 'hours'.
|
||||||
|
if random.random() < 0.1666 or scheduler.interval_unit in ('now', 'hours'):
|
||||||
|
ex_s = exception_to_unicode(exception)
|
||||||
|
try:
|
||||||
|
event, template = scheduler.event_id, scheduler.template_id
|
||||||
|
emails = list(set([event.organizer_id.email, event.user_id.email, template.write_uid.email]))
|
||||||
|
subject = _("WARNING: Event Scheduler Error for event: %s" % event.name)
|
||||||
|
body = _("""Event Scheduler for:
|
||||||
|
- Event: %s (%s)
|
||||||
|
- Scheduled: %s
|
||||||
|
- Template: %s (%s)
|
||||||
|
|
||||||
|
Failed with error:
|
||||||
|
- %s
|
||||||
|
|
||||||
|
You receive this email because you are:
|
||||||
|
- the organizer of the event,
|
||||||
|
- or the responsible of the event,
|
||||||
|
- or the last writer of the template."""
|
||||||
|
% (event.name, event.id, scheduler.scheduled_date, template.name, template.id, ex_s))
|
||||||
|
email = self.env['ir.mail_server'].build_email(
|
||||||
|
email_from=self.env.user.email,
|
||||||
|
email_to=emails,
|
||||||
|
subject=subject, body=body,
|
||||||
|
)
|
||||||
|
self.env['ir.mail_server'].send_email(email)
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error("Exception while sending traceback by email: %s.\n Original Traceback:\n%s", e, exception)
|
||||||
|
pass
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def run(self, autocommit=False):
|
def run(self, autocommit=False):
|
||||||
schedulers = self.search([('done', '=', False), ('scheduled_date', '<=', datetime.strftime(fields.datetime.now(), tools.DEFAULT_SERVER_DATETIME_FORMAT))])
|
schedulers = self.search([('done', '=', False), ('scheduled_date', '<=', datetime.strftime(fields.datetime.now(), tools.DEFAULT_SERVER_DATETIME_FORMAT))])
|
||||||
for scheduler in schedulers:
|
for scheduler in schedulers:
|
||||||
scheduler.execute()
|
try:
|
||||||
if autocommit:
|
with self.env.cr.savepoint():
|
||||||
self.env.cr.commit()
|
scheduler.execute()
|
||||||
|
except Exception as e:
|
||||||
|
_logger.exception(e)
|
||||||
|
self.invalidate_cache()
|
||||||
|
self._warn_template_error(scheduler, e)
|
||||||
|
else:
|
||||||
|
if autocommit:
|
||||||
|
self.env.cr.commit()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ class ResPartner(models.Model):
|
|||||||
event_count = fields.Integer("Events", compute='_compute_event_count', help="Number of events the partner has participated.")
|
event_count = fields.Integer("Events", compute='_compute_event_count', help="Number of events the partner has participated.")
|
||||||
|
|
||||||
def _compute_event_count(self):
|
def _compute_event_count(self):
|
||||||
|
if not self.user_has_groups('event.group_event_user'):
|
||||||
|
return
|
||||||
for partner in self:
|
for partner in self:
|
||||||
partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
|
partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<img t-att-src="'data:image/png;base64,%s' % to_text(o.event_id.organizer_id.company_id.logo_web)" style="max-height:1cm; max-width:4cm;"/>
|
<img t-att-src="'data:image/png;base64,%s' % to_text(o.event_id.organizer_id.company_id.logo_web)" style="max-height:1cm; max-width:4cm;"/>
|
||||||
</span>
|
</span>
|
||||||
<h5 t-field="o.event_id.name"/>
|
<h5 t-field="o.event_id.name"/>
|
||||||
<h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.date_begin" t-options='{"hide_seconds": True}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.date_end" t-options='{"hide_seconds": True}'> </span> )</h5>
|
<h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.with_context(tz=o.event_id.date_tz).date_begin" t-options='{"hide_seconds": True}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.with_context(tz=o.event_id.date_tz).date_end" t-options='{"hide_seconds": True}'> </span> )</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 text-center" id="o_event_name">
|
<div class="col-xs-12 text-center" id="o_event_name">
|
||||||
|
@ -41,5 +41,12 @@
|
|||||||
]
|
]
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record model="ir.rule" id="event_registration_portal">
|
||||||
|
<field name="name">Event/Registration: Portal</field>
|
||||||
|
<field name="model_id" ref="model_event_registration"/>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||||
|
<field name="domain_force">['|', ('email', '=', user.partner_id.email), ('partner_id', '=', user.partner_id.id)]
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</flectra>
|
</flectra>
|
||||||
|
@ -5,7 +5,8 @@ access_event_event_portal,event.event.portal,model_event_event,,1,0,0,0
|
|||||||
access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0
|
access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0
|
||||||
access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1
|
access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1
|
||||||
access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1
|
access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1
|
||||||
access_event_registration_portal,event.registration,model_event_registration,,1,0,0,0
|
access_event_registration_employee,event.registration,model_event_registration,base.group_user,1,0,0,0
|
||||||
|
access_event_registration_portal,event.registration,model_event_registration,base.group_portal,1,0,0,0
|
||||||
access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0
|
access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0
|
||||||
access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1
|
access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1
|
||||||
access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0
|
access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0
|
||||||
|
|
@ -5,11 +5,13 @@
|
|||||||
<field name="name">view.res.partner.form.event.inherited</field>
|
<field name="name">view.res.partner.form.event.inherited</field>
|
||||||
<field name="model">res.partner</field>
|
<field name="model">res.partner</field>
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="groups_id" eval="[(4, ref('event.group_event_user'))]"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<div name="button_box" position="inside">
|
<div name="button_box" position="inside">
|
||||||
<button class="oe_stat_button"
|
<button class="oe_stat_button"
|
||||||
type="object"
|
type="object"
|
||||||
icon="fa-ticket"
|
icon="fa-ticket"
|
||||||
|
groups="event.group_event_user"
|
||||||
name="action_event_view" attrs="{'invisible': [('event_count','=', 0)]}">
|
name="action_event_view" attrs="{'invisible': [('event_count','=', 0)]}">
|
||||||
<field string="Events" name="event_count" widget="statinfo"/>
|
<field string="Events" name="event_count" widget="statinfo"/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -27,6 +27,7 @@ this event.
|
|||||||
'data/event_sale_data.xml',
|
'data/event_sale_data.xml',
|
||||||
'report/event_event_templates.xml',
|
'report/event_event_templates.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
'security/event_security.xml',
|
||||||
'wizard/event_edit_registration.xml',
|
'wizard/event_edit_registration.xml',
|
||||||
],
|
],
|
||||||
'demo': ['data/event_demo.xml'],
|
'demo': ['data/event_demo.xml'],
|
||||||
|
@ -41,7 +41,7 @@ class SaleOrderLine(models.Model):
|
|||||||
order line has a product_uom_qty attribute that will be the number of
|
order line has a product_uom_qty attribute that will be the number of
|
||||||
registrations linked to this line. This method update existing registrations
|
registrations linked to this line. This method update existing registrations
|
||||||
and create new one for missing one. """
|
and create new one for missing one. """
|
||||||
Registration = self.env['event.registration']
|
Registration = self.env['event.registration'].sudo()
|
||||||
registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
|
registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
|
||||||
for so_line in self.filtered('event_id'):
|
for so_line in self.filtered('event_id'):
|
||||||
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
|
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
|
||||||
|
8
addons/event_sale/security/event_security.xml
Normal file
8
addons/event_sale/security/event_security.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<flectra>
|
||||||
|
<data noupdate="0">
|
||||||
|
<record id="sales_team.group_sale_salesman" model="res.groups">
|
||||||
|
<field name="implied_ids" eval="[(4, ref('event.group_event_user'))]"/>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</flectra>
|
@ -19,9 +19,10 @@ class FleetVehicle(models.Model):
|
|||||||
name = fields.Char(compute="_compute_vehicle_name", store=True)
|
name = fields.Char(compute="_compute_vehicle_name", store=True)
|
||||||
active = fields.Boolean('Active', default=True, track_visibility="onchange")
|
active = fields.Boolean('Active', default=True, track_visibility="onchange")
|
||||||
company_id = fields.Many2one('res.company', 'Company')
|
company_id = fields.Many2one('res.company', 'Company')
|
||||||
license_plate = fields.Char(required=True, help='License plate number of the vehicle (i = plate number for a car)')
|
license_plate = fields.Char(required=True, track_visibility="onchange",
|
||||||
|
help='License plate number of the vehicle (i = plate number for a car)')
|
||||||
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
||||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle', copy=False)
|
driver_id = fields.Many2one('res.partner', 'Driver', track_visibility="onchange", help='Driver of the vehicle', copy=False)
|
||||||
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
||||||
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
||||||
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
||||||
@ -74,10 +75,10 @@ class FleetVehicle(models.Model):
|
|||||||
('driver_id_unique', 'UNIQUE(driver_id)', 'Only one car can be assigned to the same employee!')
|
('driver_id_unique', 'UNIQUE(driver_id)', 'Only one car can be assigned to the same employee!')
|
||||||
]
|
]
|
||||||
|
|
||||||
@api.depends('model_id', 'license_plate')
|
@api.depends('model_id.brand_id.name', 'model_id.name', 'license_plate')
|
||||||
def _compute_vehicle_name(self):
|
def _compute_vehicle_name(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + record.license_plate
|
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + (record.license_plate or _('No Plate'))
|
||||||
|
|
||||||
def _get_odometer(self):
|
def _get_odometer(self):
|
||||||
FleetVehicalOdometer = self.env['fleet.vehicle.odometer']
|
FleetVehicalOdometer = self.env['fleet.vehicle.odometer']
|
||||||
|
@ -259,7 +259,7 @@ class Goal(models.Model):
|
|||||||
If the end date is passed (at least +1 day, time not considered) without
|
If the end date is passed (at least +1 day, time not considered) without
|
||||||
the target value being reached, the goal is set as failed."""
|
the target value being reached, the goal is set as failed."""
|
||||||
goals_by_definition = {}
|
goals_by_definition = {}
|
||||||
for goal in self:
|
for goal in self.with_context(prefetch_fields=False):
|
||||||
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
|
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
|
||||||
|
|
||||||
for definition, goals in goals_by_definition.items():
|
for definition, goals in goals_by_definition.items():
|
||||||
|
@ -677,7 +677,7 @@ class GoogleCalendar(models.AbstractModel):
|
|||||||
try:
|
try:
|
||||||
all_event_from_google = self.get_event_synchro_dict(lastSync=lastSync)
|
all_event_from_google = self.get_event_synchro_dict(lastSync=lastSync)
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
if e.response.code == 410: # GONE, Google is lost.
|
if e.response.status_code == 410: # GONE, Google is lost.
|
||||||
# we need to force the rollback from this cursor, because it locks my res_users but I need to write in this tuple before to raise.
|
# we need to force the rollback from this cursor, because it locks my res_users but I need to write in this tuple before to raise.
|
||||||
self.env.cr.rollback()
|
self.env.cr.rollback()
|
||||||
self.env.user.write({'google_calendar_last_sync_date': False})
|
self.env.user.write({'google_calendar_last_sync_date': False})
|
||||||
@ -842,7 +842,7 @@ class GoogleCalendar(models.AbstractModel):
|
|||||||
try:
|
try:
|
||||||
# if already deleted from gmail or never created
|
# if already deleted from gmail or never created
|
||||||
recs.delete_an_event(current_event[0])
|
recs.delete_an_event(current_event[0])
|
||||||
except Exception as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
if e.response.status_code in (401, 410,):
|
if e.response.status_code in (401, 410,):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">res.config.settings</field>
|
<field name="res_model">res.config.settings</field>
|
||||||
<field name="view_mode">form</field>
|
<field name="view_mode">form</field>
|
||||||
|
<field name="context">{'module' : 'general_settings'}</field>
|
||||||
<field name="target">inline</field>
|
<field name="target">inline</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
# 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 collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
from flectra import http, _
|
from flectra import http, _
|
||||||
from flectra.http import request
|
from flectra.http import request
|
||||||
from flectra.addons.portal.controllers.portal import get_records_pager, CustomerPortal, pager as portal_pager
|
from flectra.addons.portal.controllers.portal import get_records_pager, CustomerPortal, pager as portal_pager
|
||||||
|
from flectra.tools import groupby as groupbyelem
|
||||||
|
|
||||||
from flectra.osv.expression import OR
|
from flectra.osv.expression import OR
|
||||||
|
|
||||||
@ -78,6 +80,7 @@ class CustomerPortal(CustomerPortal):
|
|||||||
|
|
||||||
@http.route(['/my/tasks', '/my/tasks/page/<int:page>'], type='http', auth="user", website=True)
|
@http.route(['/my/tasks', '/my/tasks/page/<int:page>'], type='http', auth="user", website=True)
|
||||||
def portal_my_tasks(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, search=None, search_in='content', **kw):
|
def portal_my_tasks(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, search=None, search_in='content', **kw):
|
||||||
|
groupby = kw.get('groupby', 'project') #TODO master fix this
|
||||||
values = self._prepare_portal_layout_values()
|
values = self._prepare_portal_layout_values()
|
||||||
searchbar_sortings = {
|
searchbar_sortings = {
|
||||||
'date': {'label': _('Newest'), 'order': 'create_date desc'},
|
'date': {'label': _('Newest'), 'order': 'create_date desc'},
|
||||||
@ -95,6 +98,10 @@ class CustomerPortal(CustomerPortal):
|
|||||||
'stage': {'input': 'stage', 'label': _('Search in Stages')},
|
'stage': {'input': 'stage', 'label': _('Search in Stages')},
|
||||||
'all': {'input': 'all', 'label': _('Search in All')},
|
'all': {'input': 'all', 'label': _('Search in All')},
|
||||||
}
|
}
|
||||||
|
searchbar_groupby = {
|
||||||
|
'none': {'input': 'none', 'label': _('None')},
|
||||||
|
'project': {'input': 'project', 'label': _('Project')},
|
||||||
|
}
|
||||||
# extends filterby criteria with project (criteria name is the project id)
|
# extends filterby criteria with project (criteria name is the project id)
|
||||||
# Note: portal users can't view projects they don't follow
|
# Note: portal users can't view projects they don't follow
|
||||||
projects = request.env['project.project'].sudo().search([('privacy_visibility', '=', 'portal')])
|
projects = request.env['project.project'].sudo().search([('privacy_visibility', '=', 'portal')])
|
||||||
@ -136,28 +143,37 @@ class CustomerPortal(CustomerPortal):
|
|||||||
# pager
|
# pager
|
||||||
pager = portal_pager(
|
pager = portal_pager(
|
||||||
url="/my/tasks",
|
url="/my/tasks",
|
||||||
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby},
|
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby, 'search_in': search_in, 'search': search},
|
||||||
total=task_count,
|
total=task_count,
|
||||||
page=page,
|
page=page,
|
||||||
step=self._items_per_page
|
step=self._items_per_page
|
||||||
)
|
)
|
||||||
# content according to pager and archive selected
|
# content according to pager and archive selected
|
||||||
|
if groupby == 'project':
|
||||||
|
order = "project_id, %s" % order # force sort on project first to group by project in view
|
||||||
tasks = request.env['project.task'].search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
|
tasks = request.env['project.task'].search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
|
||||||
request.session['my_tasks_history'] = tasks.ids[:100]
|
request.session['my_tasks_history'] = tasks.ids[:100]
|
||||||
|
if groupby == 'project':
|
||||||
|
grouped_tasks = [request.env['project.task'].concat(*g) for k, g in groupbyelem(tasks, itemgetter('project_id'))]
|
||||||
|
else:
|
||||||
|
grouped_tasks = [tasks]
|
||||||
|
|
||||||
values.update({
|
values.update({
|
||||||
'date': date_begin,
|
'date': date_begin,
|
||||||
'date_end': date_end,
|
'date_end': date_end,
|
||||||
'projects': projects,
|
'projects': projects,
|
||||||
'tasks': tasks,
|
'tasks': tasks,
|
||||||
|
'grouped_tasks': grouped_tasks,
|
||||||
'page_name': 'task',
|
'page_name': 'task',
|
||||||
'archive_groups': archive_groups,
|
'archive_groups': archive_groups,
|
||||||
'default_url': '/my/tasks',
|
'default_url': '/my/tasks',
|
||||||
'pager': pager,
|
'pager': pager,
|
||||||
'searchbar_sortings': searchbar_sortings,
|
'searchbar_sortings': searchbar_sortings,
|
||||||
|
'searchbar_groupby': searchbar_groupby,
|
||||||
'searchbar_inputs': searchbar_inputs,
|
'searchbar_inputs': searchbar_inputs,
|
||||||
'search_in': search_in,
|
'search_in': search_in,
|
||||||
'sortby': sortby,
|
'sortby': sortby,
|
||||||
|
'groupby': groupby,
|
||||||
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
|
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
|
||||||
'filterby': filterby,
|
'filterby': filterby,
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from flectra import api, fields, models, tools, SUPERUSER_ID, _
|
from flectra import api, fields, models, tools, SUPERUSER_ID, _
|
||||||
from flectra.exceptions import UserError, AccessError
|
from flectra.exceptions import UserError, AccessError, ValidationError
|
||||||
from flectra.tools.safe_eval import safe_eval
|
from flectra.tools.safe_eval import safe_eval
|
||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
|
|
||||||
@ -469,7 +469,7 @@ class Task(models.Model):
|
|||||||
legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True, related_sudo=False)
|
legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True, related_sudo=False)
|
||||||
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True, related_sudo=False)
|
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True, related_sudo=False)
|
||||||
parent_id = fields.Many2one('project.task', string='Parent Task', index=True)
|
parent_id = fields.Many2one('project.task', string='Parent Task', index=True)
|
||||||
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks")
|
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks", context={'active_test': False})
|
||||||
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
|
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
|
||||||
subtask_count = fields.Integer(compute='_compute_subtask_count', type='integer', string="Sub-task count")
|
subtask_count = fields.Integer(compute='_compute_subtask_count', type='integer', string="Sub-task count")
|
||||||
email_from = fields.Char(string='Email', help="These people will receive email.", index=True)
|
email_from = fields.Char(string='Email', help="These people will receive email.", index=True)
|
||||||
@ -598,6 +598,12 @@ class Task(models.Model):
|
|||||||
for task in self:
|
for task in self:
|
||||||
task.subtask_count = self.search_count([('id', 'child_of', task.id), ('id', '!=', task.id)])
|
task.subtask_count = self.search_count([('id', 'child_of', task.id), ('id', '!=', task.id)])
|
||||||
|
|
||||||
|
@api.constrains('parent_id')
|
||||||
|
def _check_parent_id(self):
|
||||||
|
for task in self:
|
||||||
|
if not task._check_recursion():
|
||||||
|
raise ValidationError(_('Error! You cannot create recursive hierarchy of task(s).'))
|
||||||
|
|
||||||
@api.constrains('parent_id')
|
@api.constrains('parent_id')
|
||||||
def _check_subtask_project(self):
|
def _check_subtask_project(self):
|
||||||
for task in self:
|
for task in self:
|
||||||
@ -796,7 +802,7 @@ class Task(models.Model):
|
|||||||
|
|
||||||
groups = [new_group] + groups
|
groups = [new_group] + groups
|
||||||
for group_name, group_method, group_data in groups:
|
for group_name, group_method, group_data in groups:
|
||||||
if group_name in ['customer', 'portal']:
|
if group_name == 'customer':
|
||||||
continue
|
continue
|
||||||
group_data['has_button_access'] = True
|
group_data['has_button_access'] = True
|
||||||
|
|
||||||
@ -874,12 +880,12 @@ class Task(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def message_get_suggested_recipients(self):
|
def message_get_suggested_recipients(self):
|
||||||
recipients = super(Task, self).message_get_suggested_recipients()
|
recipients = super(Task, self).message_get_suggested_recipients()
|
||||||
for task in self.filtered('partner_id'):
|
for task in self:
|
||||||
reason = _('Customer Email') if task.partner_id.email else _('Customer')
|
|
||||||
if task.partner_id:
|
if task.partner_id:
|
||||||
|
reason = _('Customer Email') if task.partner_id.email else _('Customer')
|
||||||
task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason)
|
task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason)
|
||||||
elif task.email_from:
|
elif task.email_from:
|
||||||
task._message_add_suggested_recipient(recipients, partner=task.email_from, reason=reason)
|
task._message_add_suggested_recipient(recipients, email=task.email_from, reason=_('Customer Email'))
|
||||||
return recipients
|
return recipients
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@ -926,6 +932,20 @@ class Task(models.Model):
|
|||||||
'type': 'ir.actions.act_window'
|
'type': 'ir.actions.act_window'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def action_subtask(self):
|
||||||
|
action = self.env.ref('project.project_task_action_sub_task').read()[0]
|
||||||
|
ctx = self.env.context.copy()
|
||||||
|
ctx.update({
|
||||||
|
'default_parent_id' : self.id,
|
||||||
|
'default_project_id' : self.env.context.get('project_id', self.subtask_project_id.id),
|
||||||
|
'default_name' : self.env.context.get('name', self.name) + ':',
|
||||||
|
'default_partner_id' : self.env.context.get('partner_id', self.partner_id.id),
|
||||||
|
'search_default_project_id': self.env.context.get('project_id', self.subtask_project_id.id),
|
||||||
|
})
|
||||||
|
action['context'] = ctx
|
||||||
|
action['domain'] = [('id', 'child_of', self.id), ('id', '!=', self.id)]
|
||||||
|
return action
|
||||||
|
|
||||||
|
|
||||||
class AccountAnalyticAccount(models.Model):
|
class AccountAnalyticAccount(models.Model):
|
||||||
_inherit = 'account.analytic.account'
|
_inherit = 'account.analytic.account'
|
||||||
|
@ -130,36 +130,41 @@
|
|||||||
<h3>Tasks
|
<h3>Tasks
|
||||||
<t t-call="portal.portal_searchbar"/>
|
<t t-call="portal.portal_searchbar"/>
|
||||||
</h3>
|
</h3>
|
||||||
<t t-if="not tasks">
|
<t t-if="not grouped_tasks">
|
||||||
<div class="alert alert-warning mt8" role="alert">
|
<div class="alert alert-warning mt8" role="alert">
|
||||||
There are no tasks.
|
There are no tasks.
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<div t-if="tasks" class="panel panel-default">
|
<div t-if="grouped_tasks" class="panel panel-default">
|
||||||
<div class="table-responsive"><table class="table table-hover o_portal_my_doc_table">
|
<div class="table-responsive">
|
||||||
<thead>
|
<t t-foreach="grouped_tasks" t-as="tasks">
|
||||||
<tr class="active">
|
<table class="table table-hover o_portal_my_doc_table">
|
||||||
<th class="col-md-9">Task</th>
|
<thead>
|
||||||
<th class="col-md-2">Stage</th>
|
<tr class="active">
|
||||||
<th>Ref</th>
|
<th t-if="groupby == 'none'" class="col-md-9">Name</th>
|
||||||
</tr>
|
<th t-else="" class="col-md-9"><span t-field="tasks[0].project_id.label_tasks"/> for project: <span t-field="tasks[0].project_id.name"/></th>
|
||||||
</thead>
|
<th class="col-md-2">Stage</th>
|
||||||
<tbody>
|
<th>Ref</th>
|
||||||
<t t-foreach="tasks" t-as="task">
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<td>
|
<tbody>
|
||||||
<a t-attf-href="/my/task/#{task.id}?{{ keep_query() }}"><span t-field="task.name"/></a>
|
<t t-foreach="tasks" t-as="task">
|
||||||
</td>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-info" title="Current stage of the task" t-esc="task.stage_id.name" />
|
<a t-attf-href="/my/task/#{task.id}?{{ keep_query() }}"><span t-field="task.name"/></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span t-esc="task.id"/>
|
<span class="label label-info" title="Current stage of the task" t-esc="task.stage_id.name" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td>
|
||||||
</t>
|
<span t-esc="task.id"/>
|
||||||
</tbody>
|
</td>
|
||||||
</table></div>
|
</tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="pager" class="o_portal_pager text-center">
|
<div t-if="pager" class="o_portal_pager text-center">
|
||||||
<t t-call="portal.pager"/>
|
<t t-call="portal.pager"/>
|
||||||
|
@ -413,7 +413,7 @@
|
|||||||
<sheet string="Task">
|
<sheet string="Task">
|
||||||
<div class="oe_button_box" name="button_box">
|
<div class="oe_button_box" name="button_box">
|
||||||
<button class="oe_stat_button" icon="fa-tasks" type="object" name="action_open_parent_task" string="Parent Task" attrs="{'invisible' : [('parent_id', '=', False)]}" groups="project.group_subtask_project"/>
|
<button class="oe_stat_button" icon="fa-tasks" type="object" name="action_open_parent_task" string="Parent Task" attrs="{'invisible' : [('parent_id', '=', False)]}" groups="project.group_subtask_project"/>
|
||||||
<button name="%(project_task_action_sub_task)d" type="action" class="oe_stat_button" icon="fa-tasks"
|
<button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
|
||||||
attrs="{'invisible' : [('parent_id', '!=', False)]}"
|
attrs="{'invisible' : [('parent_id', '!=', False)]}"
|
||||||
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" groups="project.group_subtask_project">
|
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" groups="project.group_subtask_project">
|
||||||
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
|
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- 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 datetime import datetime
|
from datetime import datetime, time
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from flectra import fields
|
from flectra import fields
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<!--Email template -->
|
<!--Email template -->
|
||||||
<record id="email_template_edi_purchase" model="mail.template">
|
<record id="email_template_edi_purchase" model="mail.template">
|
||||||
<field name="name">RFQ - Send by Email</field>
|
<field name="name">RFQ - Send by Email</field>
|
||||||
<field name="email_from">${(object.create_uid.email and '%s <%s>' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
|
<field name="email_from">${(object.create_uid.email and '"%s" <%s>' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
|
||||||
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
|
<field name="subject">${object.company_id.name} 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="purchase.model_purchase_order"/>
|
<field name="model_id" ref="purchase.model_purchase_order"/>
|
||||||
@ -46,7 +46,7 @@ from ${object.company_id.name}.
|
|||||||
<!--Email template -->
|
<!--Email template -->
|
||||||
<record id="email_template_edi_purchase_done" model="mail.template">
|
<record id="email_template_edi_purchase_done" model="mail.template">
|
||||||
<field name="name">Purchase Order - Send by Email</field>
|
<field name="name">Purchase Order - Send by Email</field>
|
||||||
<field name="email_from">${(object.create_uid.email and '%s <%s>' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
|
<field name="email_from">${(object.create_uid.email and '"%s" <%s>' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
|
||||||
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
|
<field name="subject">${object.company_id.name} 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="purchase.model_purchase_order"/>
|
<field name="model_id" ref="purchase.model_purchase_order"/>
|
||||||
@ -86,6 +86,7 @@ from ${object.company_id.name}.
|
|||||||
<field name="auto_delete" eval="True"/>
|
<field name="auto_delete" eval="True"/>
|
||||||
<field name="body_html"><![CDATA[<html>
|
<field name="body_html"><![CDATA[<html>
|
||||||
<head></head>
|
<head></head>
|
||||||
|
% 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" summary="o_mail_notification">
|
<table border="0" width="100%" cellpadding="0" bgcolor="#ededed" style="padding: 20px; background-color: #ededed" summary="o_mail_notification">
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -101,7 +102,7 @@ from ${object.company_id.name}.
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td valign="middle" align="right">
|
<td valign="middle" align="right">
|
||||||
<img src="/logo.png" style="padding: 0px; margin: 0px; height: auto; width: 80px;" alt="${user.company_id.name}">
|
<img src="/logo.png?company=${company.id}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" alt="${company.name}">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@ -127,16 +128,16 @@ from ${object.company_id.name}.
|
|||||||
<table width="590" border="0" cellpadding="0" bgcolor="#875A7B" style="min-width: 590px; background-color: rgb(135,90,123); padding: 20px;">
|
<table width="590" border="0" cellpadding="0" bgcolor="#875A7B" style="min-width: 590px; background-color: rgb(135,90,123); padding: 20px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td valign="middle" align="left" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
|
<td valign="middle" align="left" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
|
||||||
${user.company_id.name}<br/>
|
${company.name}<br/>
|
||||||
${user.company_id.phone or ''}
|
${company.phone or ''}
|
||||||
</td>
|
</td>
|
||||||
<td valign="middle" align="right" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
|
<td valign="middle" align="right" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
|
||||||
% if user.company_id.email:
|
% if company.email:
|
||||||
<a href="mailto:${user.company_id.email}" style="text-decoration:none; color: white;">${user.company_id.email}</a><br/>
|
<a href="mailto:${company.email}" style="text-decoration:none; color: white;">${company.email}</a><br/>
|
||||||
% endif
|
% endif
|
||||||
% if user.company_id.website:
|
% if company.website:
|
||||||
<a href="${user.company_id.website}" style="text-decoration:none; color: white;">
|
<a href="${company.website}" style="text-decoration:none; color: white;">
|
||||||
${user.company_id.website}
|
${company.website}
|
||||||
</a>
|
</a>
|
||||||
% endif
|
% endif
|
||||||
</td>
|
</td>
|
||||||
|
@ -6,3 +6,6 @@
|
|||||||
warehouse = self.browse(ref('stock.' + wh_ref))
|
warehouse = self.browse(ref('stock.' + wh_ref))
|
||||||
#Force the rewriting of route and rule
|
#Force the rewriting of route and rule
|
||||||
warehouse.write({'buy_to_resupply': True})
|
warehouse.write({'buy_to_resupply': True})
|
||||||
|
partner_id = ref('stock.res_partner_company_1')
|
||||||
|
warehouse = self.search([('partner_id', '=', partner_id)])
|
||||||
|
warehouse.write({'buy_to_resupply': True})
|
||||||
|
@ -161,7 +161,15 @@ class AccountInvoice(models.Model):
|
|||||||
#for average/fifo/lifo costing method, fetch real cost price from incomming moves
|
#for average/fifo/lifo costing method, fetch real cost price from incomming moves
|
||||||
valuation_price_unit = i_line.purchase_line_id.product_uom._compute_price(i_line.purchase_line_id.price_unit, i_line.uom_id)
|
valuation_price_unit = i_line.purchase_line_id.product_uom._compute_price(i_line.purchase_line_id.price_unit, i_line.uom_id)
|
||||||
stock_move_obj = self.env['stock.move']
|
stock_move_obj = self.env['stock.move']
|
||||||
valuation_stock_move = stock_move_obj.search([('purchase_line_id', '=', i_line.purchase_line_id.id), ('state', '=', 'done')])
|
valuation_stock_move = stock_move_obj.search([
|
||||||
|
('purchase_line_id', '=', i_line.purchase_line_id.id),
|
||||||
|
('state', '=', 'done'), ('product_qty', '!=', 0.0)
|
||||||
|
])
|
||||||
|
if self.type == 'in_refund':
|
||||||
|
valuation_stock_move = valuation_stock_move.filtered(lambda m: m._is_out())
|
||||||
|
elif self.type == 'in_invoice':
|
||||||
|
valuation_stock_move = valuation_stock_move.filtered(lambda m: m._is_in())
|
||||||
|
|
||||||
if valuation_stock_move:
|
if valuation_stock_move:
|
||||||
valuation_price_unit_total = 0
|
valuation_price_unit_total = 0
|
||||||
valuation_total_qty = 0
|
valuation_total_qty = 0
|
||||||
|
@ -96,7 +96,7 @@ class PurchaseOrder(models.Model):
|
|||||||
@api.depends('picking_ids', 'picking_ids.state')
|
@api.depends('picking_ids', 'picking_ids.state')
|
||||||
def _compute_is_shipped(self):
|
def _compute_is_shipped(self):
|
||||||
for order in self:
|
for order in self:
|
||||||
if order.picking_ids and all([x.state == 'done' for x in order.picking_ids]):
|
if order.picking_ids and all([x.state in ['done', 'cancel'] for x in order.picking_ids]):
|
||||||
order.is_shipped = True
|
order.is_shipped = True
|
||||||
|
|
||||||
@api.constrains('picking_type_id', 'branch_id')
|
@api.constrains('picking_type_id', 'branch_id')
|
||||||
@ -352,14 +352,15 @@ class PurchaseOrder(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def print_quotation(self):
|
def print_quotation(self):
|
||||||
|
self.write({'state': "sent"})
|
||||||
return self.env.ref('purchase.report_purchase_quotation').report_action(self)
|
return self.env.ref('purchase.report_purchase_quotation').report_action(self)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_approve(self, force=False):
|
def button_approve(self, force=False):
|
||||||
self.write({'state': 'purchase', 'date_approve': fields.Date.context_today(self)})
|
self.write({'state': 'purchase', 'date_approve': fields.Date.context_today(self)})
|
||||||
self._create_picking()
|
self._create_picking()
|
||||||
if self.company_id.po_lock == 'lock':
|
self.filtered(
|
||||||
self.write({'state': 'done'})
|
lambda p: p.company_id.po_lock == 'lock').write({'state': 'done'})
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@ -406,6 +407,8 @@ class PurchaseOrder(models.Model):
|
|||||||
for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
|
for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
|
||||||
pick.action_cancel()
|
pick.action_cancel()
|
||||||
|
|
||||||
|
order.order_line.write({'move_dest_ids':[(5,0,0)]})
|
||||||
|
|
||||||
self.write({'state': 'cancel'})
|
self.write({'state': 'cancel'})
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@ -940,12 +943,13 @@ class ProcurementRule(models.Model):
|
|||||||
if domain in cache:
|
if domain in cache:
|
||||||
po = cache[domain]
|
po = cache[domain]
|
||||||
else:
|
else:
|
||||||
po = self.env['purchase.order'].search([dom for dom in domain])
|
po = self.env['purchase.order'].sudo().search([dom for dom in domain])
|
||||||
po = po[0] if po else False
|
po = po[0] if po else False
|
||||||
cache[domain] = po
|
cache[domain] = po
|
||||||
if not po:
|
if not po:
|
||||||
vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner)
|
vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner)
|
||||||
po = self.env['purchase.order'].create(vals)
|
company_id = values.get('company_id') and values['company_id'].id or self.env.user.company_id.id
|
||||||
|
po = self.env['purchase.order'].with_context(force_company=company_id).sudo().create(vals)
|
||||||
cache[domain] = po
|
cache[domain] = po
|
||||||
elif not po.origin or origin not in po.origin.split(', '):
|
elif not po.origin or origin not in po.origin.split(', '):
|
||||||
if po.origin:
|
if po.origin:
|
||||||
@ -966,7 +970,7 @@ class ProcurementRule(models.Model):
|
|||||||
break
|
break
|
||||||
if not po_line:
|
if not po_line:
|
||||||
vals = self._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier)
|
vals = self._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier)
|
||||||
self.env['purchase.order.line'].create(vals)
|
self.env['purchase.order.line'].sudo().create(vals)
|
||||||
|
|
||||||
def _get_purchase_schedule_date(self, values):
|
def _get_purchase_schedule_date(self, values):
|
||||||
"""Return the datetime value to use as Schedule Date (``date_planned``) for the
|
"""Return the datetime value to use as Schedule Date (``date_planned``) for the
|
||||||
@ -1049,20 +1053,20 @@ class ProcurementRule(models.Model):
|
|||||||
def _prepare_purchase_order(self, product_id, product_qty, product_uom, origin, values, partner):
|
def _prepare_purchase_order(self, product_id, product_qty, product_uom, origin, values, partner):
|
||||||
schedule_date = self._get_purchase_schedule_date(values)
|
schedule_date = self._get_purchase_schedule_date(values)
|
||||||
purchase_date = self._get_purchase_order_date(product_id, product_qty, product_uom, values, partner, schedule_date)
|
purchase_date = self._get_purchase_order_date(product_id, product_qty, product_uom, values, partner, schedule_date)
|
||||||
fpos = self.env['account.fiscal.position'].with_context(company_id=values['company_id'].id).get_fiscal_position(partner.id)
|
fpos = self.env['account.fiscal.position'].with_context(force_company=values['company_id'].id).get_fiscal_position(partner.id)
|
||||||
|
|
||||||
gpo = self.group_propagation_option
|
gpo = self.group_propagation_option
|
||||||
group = (gpo == 'fixed' and self.group_id.id) or \
|
group = (gpo == 'fixed' and self.group_id.id) or \
|
||||||
(gpo == 'propagate' and values['group_id'].id) or False
|
(gpo == 'propagate' and values.get('group_id') and values['group_id'].id) or False
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'partner_id': partner.id,
|
'partner_id': partner.id,
|
||||||
'picking_type_id': self.picking_type_id.id,
|
'picking_type_id': self.picking_type_id.id,
|
||||||
'company_id': values['company_id'].id,
|
'company_id': values['company_id'].id,
|
||||||
'currency_id': partner.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
|
'currency_id': partner.with_context(force_company=values['company_id'].id).property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
|
||||||
'dest_address_id': values.get('partner_dest_id', False) and values['partner_dest_id'].id,
|
'dest_address_id': values.get('partner_dest_id', False) and values['partner_dest_id'].id,
|
||||||
'origin': origin,
|
'origin': origin,
|
||||||
'payment_term_id': partner.property_supplier_payment_term_id.id,
|
'payment_term_id': partner.with_context(force_company=values['company_id'].id).property_supplier_payment_term_id.id,
|
||||||
'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||||
'fiscal_position_id': fpos,
|
'fiscal_position_id': fpos,
|
||||||
'group_id': group
|
'group_id': group
|
||||||
@ -1078,7 +1082,7 @@ class ProcurementRule(models.Model):
|
|||||||
domain = super(ProcurementRule, self)._make_po_get_domain(values, partner)
|
domain = super(ProcurementRule, self)._make_po_get_domain(values, partner)
|
||||||
gpo = self.group_propagation_option
|
gpo = self.group_propagation_option
|
||||||
group = (gpo == 'fixed' and self.group_id) or \
|
group = (gpo == 'fixed' and self.group_id) or \
|
||||||
(gpo == 'propagate' and values['group_id']) or False
|
(gpo == 'propagate' and 'group_id' in values and values['group_id']) or False
|
||||||
|
|
||||||
domain += (
|
domain += (
|
||||||
('partner_id', '=', partner.id),
|
('partner_id', '=', partner.id),
|
||||||
|
@ -27,7 +27,7 @@ class StockMove(models.Model):
|
|||||||
_inherit = 'stock.move'
|
_inherit = 'stock.move'
|
||||||
|
|
||||||
purchase_line_id = fields.Many2one('purchase.order.line',
|
purchase_line_id = fields.Many2one('purchase.order.line',
|
||||||
'Purchase Order Line', ondelete='set null', index=True, readonly=True, copy=False)
|
'Purchase Order Line', ondelete='set null', index=True, readonly=True)
|
||||||
created_purchase_line_id = fields.Many2one('purchase.order.line',
|
created_purchase_line_id = fields.Many2one('purchase.order.line',
|
||||||
'Created Purchase Order Line', ondelete='set null', readonly=True, copy=False)
|
'Created Purchase Order Line', ondelete='set null', readonly=True, copy=False)
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class PurchaseReport(models.Model):
|
|||||||
join res_partner partner on s.partner_id = partner.id
|
join res_partner partner on s.partner_id = partner.id
|
||||||
left join product_product p on (l.product_id=p.id)
|
left join product_product p on (l.product_id=p.id)
|
||||||
left join product_template t on (p.product_tmpl_id=t.id)
|
left join product_template t on (p.product_tmpl_id=t.id)
|
||||||
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.template,',t.id) AND ip.company_id=s.company_id)
|
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.product,',p.id) AND ip.company_id=s.company_id)
|
||||||
left join product_uom u on (u.id=l.product_uom)
|
left join product_uom u on (u.id=l.product_uom)
|
||||||
left join product_uom u2 on (u2.id=t.uom_id)
|
left join product_uom u2 on (u2.id=t.uom_id)
|
||||||
left join stock_picking_type spt on (spt.id=s.picking_type_id)
|
left join stock_picking_type spt on (spt.id=s.picking_type_id)
|
||||||
|
@ -5,6 +5,7 @@ import time
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flectra.tests.common import TransactionCase
|
from flectra.tests.common import TransactionCase
|
||||||
|
from flectra.addons.account.tests.account_test_classes import AccountingTestCase
|
||||||
from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
|
|
||||||
|
|
||||||
@ -181,7 +182,7 @@ class TestStockValuation(TransactionCase):
|
|||||||
'company_id': po1.company_id.id,
|
'company_id': po1.company_id.id,
|
||||||
})
|
})
|
||||||
eur_currency._compute_current_rate()
|
eur_currency._compute_current_rate()
|
||||||
price_unit_usd_new_rate = po1.currency_id.compute(po1.order_line.price_unit, po1.company_id.currency_id, round=True)
|
price_unit_usd_new_rate = po1.currency_id.compute(po1.order_line.price_unit, po1.company_id.currency_id, round=False)
|
||||||
|
|
||||||
# the new price_unit is lower than th initial because of the rate's change
|
# the new price_unit is lower than th initial because of the rate's change
|
||||||
self.assertLess(price_unit_usd_new_rate, price_unit_usd)
|
self.assertLess(price_unit_usd_new_rate, price_unit_usd)
|
||||||
@ -195,7 +196,7 @@ class TestStockValuation(TransactionCase):
|
|||||||
wizard.process()
|
wizard.process()
|
||||||
|
|
||||||
# the unit price of the stock move has been updated to the latest value
|
# the unit price of the stock move has been updated to the latest value
|
||||||
self.assertEquals(round(move1.price_unit), round(price_unit_usd_new_rate))
|
self.assertAlmostEqual(move1.price_unit, price_unit_usd_new_rate)
|
||||||
|
|
||||||
self.assertAlmostEqual(self.product1.stock_value, price_unit_usd_new_rate * 10, delta=0.1)
|
self.assertAlmostEqual(self.product1.stock_value, price_unit_usd_new_rate * 10, delta=0.1)
|
||||||
|
|
||||||
@ -271,3 +272,138 @@ class TestStockValuation(TransactionCase):
|
|||||||
self.assertEqual(move2.price_unit, 100)
|
self.assertEqual(move2.price_unit, 100)
|
||||||
self.assertEqual(move2.product_qty, 5)
|
self.assertEqual(move2.product_qty, 5)
|
||||||
|
|
||||||
|
|
||||||
|
class TestStockValuationWithCOA(AccountingTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestStockValuationWithCOA, self).setUp()
|
||||||
|
self.supplier_location = self.env.ref('stock.stock_location_suppliers')
|
||||||
|
self.stock_location = self.env.ref('stock.stock_location_stock')
|
||||||
|
self.partner_id = self.env.ref('base.res_partner_1')
|
||||||
|
self.product1 = self.env.ref('product.product_product_8')
|
||||||
|
Account = self.env['account.account']
|
||||||
|
self.stock_input_account = Account.create({
|
||||||
|
'name': 'Stock Input',
|
||||||
|
'code': 'StockIn',
|
||||||
|
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||||
|
})
|
||||||
|
self.stock_output_account = Account.create({
|
||||||
|
'name': 'Stock Output',
|
||||||
|
'code': 'StockOut',
|
||||||
|
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||||
|
})
|
||||||
|
self.stock_valuation_account = Account.create({
|
||||||
|
'name': 'Stock Valuation',
|
||||||
|
'code': 'Stock Valuation',
|
||||||
|
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||||
|
})
|
||||||
|
self.stock_journal = self.env['account.journal'].create({
|
||||||
|
'name': 'Stock Journal',
|
||||||
|
'code': 'STJTEST',
|
||||||
|
'type': 'general',
|
||||||
|
})
|
||||||
|
self.product1.categ_id.write({
|
||||||
|
'property_stock_account_input_categ_id': self.stock_input_account.id,
|
||||||
|
'property_stock_account_output_categ_id': self.stock_output_account.id,
|
||||||
|
'property_stock_valuation_account_id': self.stock_valuation_account.id,
|
||||||
|
'property_stock_journal': self.stock_journal.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_fifo_anglosaxon_return(self):
|
||||||
|
self.env.user.company_id.anglo_saxon_accounting = True
|
||||||
|
self.product1.product_tmpl_id.cost_method = 'fifo'
|
||||||
|
self.product1.product_tmpl_id.valuation = 'real_time'
|
||||||
|
self.product1.product_tmpl_id.invoice_policy = 'delivery'
|
||||||
|
price_diff_account = self.env['account.account'].create({
|
||||||
|
'name': 'price diff account',
|
||||||
|
'code': 'price diff account',
|
||||||
|
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||||
|
})
|
||||||
|
self.product1.property_account_creditor_price_difference = price_diff_account
|
||||||
|
|
||||||
|
# Receive 10@10 ; create the vendor bill
|
||||||
|
po1 = self.env['purchase.order'].create({
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'order_line': [
|
||||||
|
(0, 0, {
|
||||||
|
'name': self.product1.name,
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'product_qty': 10.0,
|
||||||
|
'product_uom': self.product1.uom_po_id.id,
|
||||||
|
'price_unit': 10.0,
|
||||||
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
po1.button_confirm()
|
||||||
|
receipt_po1 = po1.picking_ids[0]
|
||||||
|
receipt_po1.move_lines.quantity_done = 10
|
||||||
|
receipt_po1.button_validate()
|
||||||
|
|
||||||
|
invoice_po1 = self.env['account.invoice'].create({
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'purchase_id': po1.id,
|
||||||
|
'account_id': self.partner_id.property_account_payable_id.id,
|
||||||
|
'type': 'in_invoice',
|
||||||
|
})
|
||||||
|
invoice_po1.purchase_order_change()
|
||||||
|
invoice_po1.action_invoice_open()
|
||||||
|
|
||||||
|
# Receive 10@20 ; create the vendor bill
|
||||||
|
po2 = self.env['purchase.order'].create({
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'order_line': [
|
||||||
|
(0, 0, {
|
||||||
|
'name': self.product1.name,
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'product_qty': 10.0,
|
||||||
|
'product_uom': self.product1.uom_po_id.id,
|
||||||
|
'price_unit': 20.0,
|
||||||
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
po2.button_confirm()
|
||||||
|
receipt_po2 = po2.picking_ids[0]
|
||||||
|
receipt_po2.move_lines.quantity_done = 10
|
||||||
|
receipt_po2.button_validate()
|
||||||
|
|
||||||
|
invoice_po2 = self.env['account.invoice'].create({
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'purchase_id': po2.id,
|
||||||
|
'account_id': self.partner_id.property_account_payable_id.id,
|
||||||
|
'type': 'in_invoice',
|
||||||
|
})
|
||||||
|
invoice_po2.purchase_order_change()
|
||||||
|
invoice_po2.action_invoice_open()
|
||||||
|
|
||||||
|
# valuation of product1 should be 300
|
||||||
|
self.assertEqual(self.product1.stock_value, 300)
|
||||||
|
|
||||||
|
# return the second po
|
||||||
|
stock_return_picking = self.env['stock.return.picking']\
|
||||||
|
.with_context(active_ids=receipt_po2.ids, active_id=receipt_po2.ids[0])\
|
||||||
|
.create({})
|
||||||
|
stock_return_picking.product_return_moves.quantity = 10
|
||||||
|
stock_return_picking_action = stock_return_picking.create_returns()
|
||||||
|
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
|
||||||
|
return_pick.move_lines[0].move_line_ids[0].qty_done = 10
|
||||||
|
return_pick.do_transfer()
|
||||||
|
|
||||||
|
# valuation of product1 should be 200 as the first items will be sent out
|
||||||
|
self.assertEqual(self.product1.stock_value, 200)
|
||||||
|
|
||||||
|
# create a credit note for po2
|
||||||
|
creditnote_po2 = self.env['account.invoice'].create({
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'purchase_id': po2.id,
|
||||||
|
'account_id': self.partner_id.property_account_payable_id.id,
|
||||||
|
'type': 'in_refund',
|
||||||
|
})
|
||||||
|
|
||||||
|
creditnote_po2.purchase_order_change()
|
||||||
|
creditnote_po2.invoice_line_ids[0].quantity = 10
|
||||||
|
creditnote_po2.action_invoice_open()
|
||||||
|
|
||||||
|
# check the anglo saxon entries
|
||||||
|
price_diff_entry = self.env['account.move.line'].search([('account_id', '=', price_diff_account.id)])
|
||||||
|
self.assertEqual(price_diff_entry.credit, 100)
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<strong>The ordered quantity has been updated.</strong>
|
<strong>The ordered quantity has been updated.</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li><t t-esc="line.order_id.product_id.display_name"/>:</li>
|
<li><t t-esc="line.product_id.display_name"/>:</li>
|
||||||
Ordered Quantity: <t t-esc="line.product_qty" /> -> <t t-esc="float(product_qty)"/><br/>
|
Ordered Quantity: <t t-esc="line.product_qty" /> -> <t t-esc="float(product_qty)"/><br/>
|
||||||
<t t-if='line.order_id.product_id.type in ("consu", "product")'>
|
<t t-if='line.order_id.product_id.type in ("consu", "product")'>
|
||||||
Received Quantity: <t t-esc="line.qty_received" /><br/>
|
Received Quantity: <t t-esc="line.qty_received" /><br/>
|
||||||
|
Loading…
Reference in New Issue
Block a user