InventoryBrain.py 15 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL. All Rights Reserved.
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from Products.ZSQLCatalog.zsqlbrain import ZSQLBrain
from DateTime import DateTime
from ZTUtils import make_query
Sebastien Robin's avatar
Sebastien Robin committed
17
from Products.CMFCore.utils import getToolByName
18
from zLOG import LOG, PROBLEM
19
from Products.ERP5Type.Message import translateString
20
from ComputedAttribute import ComputedAttribute
21

Jean-Paul Smets's avatar
Jean-Paul Smets committed
22 23 24 25 26
class InventoryBrain(ZSQLBrain):
  """
    Global analysis (all variations and categories)
  """
  # Stock management
27
  def getInventory(self, at_date=None, ignore_variation=0,
Romain Courteaud's avatar
Romain Courteaud committed
28
                   simulation_state=None, **kw):
29
    if isinstance(simulation_state, str):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
30
      simulation_state = [simulation_state]
31
    result = self.Resource_zGetInventory(
Romain Courteaud's avatar
Romain Courteaud committed
32 33 34 35
                      resource_uid=[self.resource_uid],
                      to_date=at_date, omit_simulation=0,
                      section_category=self.getPortalDefaultSectionCategory(),
                      simulation_state=simulation_state)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36 37 38 39 40 41 42 43 44 45 46 47
    inventory = None
    if len(result) > 0:
      inventory = result[0].inventory
    if inventory is None:
      return 0.0
    else:
      return inventory

  def getCurrentInventory(self):
    """
      Returns current inventory
    """
Romain Courteaud's avatar
Romain Courteaud committed
48
    return self.getInventory(
49
        simulation_state=self.getPortalCurrentInventoryStateList(),
Romain Courteaud's avatar
Romain Courteaud committed
50
        ignore_variation=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
51 52 53 54 55

  def getFutureInventory(self):
    """
      Returns current inventory
    """
Romain Courteaud's avatar
Romain Courteaud committed
56 57 58 59 60 61
    return self.getInventory(
                   ignore_variation=1,
                   simulation_state= \
                       list(self.getPortalFutureInventoryStateList())+ \
                       list(self.getPortalReservedInventoryStateList())+ \
                       list(self.getPortalCurrentInventoryStateList()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
62 63 64 65 66 67 68

  def getAvailableInventory(self):
    """
      Returns current inventory
    """
    at_date=DateTime()
    current = self.getCurrentInventory()
Romain Courteaud's avatar
Romain Courteaud committed
69 70 71 72 73 74
    result = self.Resource_zGetInventory( 
                    resource_uid=[self.resource_uid], ignore_variation=1,
                    omit_simulation=1, omit_input=1,
                    section_category=self.getPortalDefaultSectionCategory(),
                    simulation_state= \
                        self.getPortalReservedInventoryStateList())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
75 76 77 78 79
    reserved_inventory = None
    if len(result) > 0:
      reserved_inventory = result[0].inventory
    if reserved_inventory is None:
      reserved_inventory = 0.0
80 81
    return current + reserved_inventory

Jean-Paul Smets's avatar
Jean-Paul Smets committed
82 83
  def getQuantityUnit(self, **kw):
    try:
Romain Courteaud's avatar
Romain Courteaud committed
84 85
      resource = self.portal_categories.unrestrictedTraverse(
                                      self.resource_relative_url)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
86
      return resource.getQuantityUnit()
Yoshinori Okuji's avatar
Yoshinori Okuji committed
87
    except AttributeError:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
88 89 90 91 92 93 94 95
      return ''

class InventoryListBrain(ZSQLBrain):
  """
    Lists each variation
  """

  # Stock management
Sebastien Robin's avatar
Sebastien Robin committed
96 97 98 99
  def getInventory(self, **kw):
    """
    Returns the inventory
    """
100
    simulation_tool = getToolByName(self, 'portal_simulation')
Romain Courteaud's avatar
Romain Courteaud committed
101 102 103 104
    return simulation_tool.getInventory(
                   node=self.node_relative_url,
                   variation_text=self.variation_text,
                   resource=self.resource_relative_url, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
105

Sebastien Robin's avatar
Sebastien Robin committed
106
  def getCurrentInventory(self,**kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
107 108 109
    """
      Returns current inventory
    """
110
    simulation_tool = getToolByName(self, 'portal_simulation')
Romain Courteaud's avatar
Romain Courteaud committed
111 112 113 114
    return simulation_tool.getCurrentInventory(
                             node=self.node_relative_url,
                             variation_text=self.variation_text,
                             resource=self.resource_relative_url, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
115

Sebastien Robin's avatar
Sebastien Robin committed
116
  def getFutureInventory(self,**kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
117 118 119
    """
      Returns current inventory
    """
Sebastien Robin's avatar
Sebastien Robin committed
120
    simulation_tool = getToolByName(self,'portal_simulation')
Romain Courteaud's avatar
Romain Courteaud committed
121 122 123 124
    return simulation_tool.getFutureInventory(
                              node=self.node_relative_url,
                              variation_text=self.variation_text,
                              resource=self.resource_relative_url, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
125

Sebastien Robin's avatar
Sebastien Robin committed
126
  def getAvailableInventory(self,**kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
127 128 129
    """
      Returns current inventory
    """
Sebastien Robin's avatar
Sebastien Robin committed
130
    simulation_tool = getToolByName(self,'portal_simulation')
131 132 133 134
    return simulation_tool.getAvailableInventory(
                             node=self.node_relative_url,
                             variation_text=self.variation_text,
                             resource=self.resource_relative_url, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
135 136

  def getQuantity(self, **kw):
137 138 139 140
    """
    Return the quantity of the current delivery for a resource
    """
    total_kw = {
141 142 143
      'movement.explanation_uid': self.getExplanationUid(),
      'movement.resource_uid': self.resource_uid,
      'movement.variation_text': self.variation_text,
144
    }
145
    total_kw.update(self.portal_catalog.buildSQLQuery(**total_kw))
146
    result = self.Delivery_zGetTotal(**total_kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
147 148 149 150 151 152 153 154 155 156
    inventory = None
    if len(result) > 0:
      inventory = result[0].inventory
    if inventory is None:
      return 0.0
    else:
      return inventory

  def getQuantityUnit(self, **kw):
    try:
157 158
      resource = self.portal_categories.unrestrictedTraverse(
                                           self.resource_relative_url)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
159
      return resource.getQuantityUnit()
Yoshinori Okuji's avatar
Yoshinori Okuji committed
160
    except AttributeError:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
161 162 163
      return ''

  def getListItemUrl(self, cname_id, selection_index, selection_name):
164 165 166 167 168 169 170
    """Returns the URL for column `cname_id`. Used by ListBox
    """
    if cname_id in ('getExplanationText', 'getExplanation', ):
      o = self.getObject()
      if o is not None:
        if not getattr(o, 'isDelivery', 0):
          explanation = o.getExplanationValue()
171
        else:
172 173 174 175
          # Additional inventory movements are catalogged in stock table
          # with the inventory's uid. Then they are their own explanation.
          explanation = o
        if explanation is not None:
176
          return '%s/%s' % (
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
                  self.portal_url.getPortalObject().absolute_url(),
                  explanation.getRelativeUrl())
      else:
        return ''
    elif getattr(self, 'resource_relative_url', None) is not None:
      # A resource is defined, so try to display the movement list
      resource = self.portal_categories.unrestrictedTraverse(
                              self.resource_relative_url)
      form_name = 'Resource_viewMovementHistory'
      query_kw = {
        'variation_text': self.variation_text,
        'selection_name': selection_name,
        'selection_index': selection_index,
        'domain_name': selection_name,
      }
      # Add parameters to query_kw
      query_kw_update = {}
194

195
      if cname_id in ('transformed_resource_title', ):
196
        return resource.absolute_url()
197
      elif cname_id in ('getCurrentInventory', ):
198
        query_kw_update = {
199 200 201 202 203 204
          'simulation_state': 
            list(self.getPortalCurrentInventoryStateList() + \
            self.getPortalTransitInventoryStateList()),
          'omit_transit': 1,
          'transit_simulation_state': list(
                 self.getPortalTransitInventoryStateList())
205
        }
206

207 208
      elif cname_id in ('getAvailableInventory', ):
        query_kw_update = {
209 210 211 212 213 214 215 216 217
          'simulation_state': list(self.getPortalCurrentInventoryStateList() + \
                            self.getPortalTransitInventoryStateList()),
          'omit_transit': 1,
          'transit_simulation_state': list(self.getPortalTransitInventoryStateList()),
          'reserved_kw': {
            'simulation_state': list(self.getPortalReservedInventoryStateList()),
            'transit_simulation_state': list(self.getPortalTransitInventoryStateList()),
            'omit_input': 1
          }
218 219 220 221 222
        }
      elif cname_id in ('getFutureInventory', 'inventory', ):
        query_kw_update = {
          'simulation_state': \
            list(self.getPortalFutureInventoryStateList()) + \
223
            list(self.getPortalTransitInventoryStateList()) + \
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
            list(self.getPortalReservedInventoryStateList()) + \
            list(self.getPortalCurrentInventoryStateList())
        }
      elif cname_id in ('getInventoryAtDate', ):
        query_kw_update = {
          'to_date': self.at_date,
          'simulation_state': \
            list(self.getPortalFutureInventoryStateList()) + \
            list(self.getPortalReservedInventoryStateList())
        }
      query_kw.update(query_kw_update)
      return '%s/%s?%s&reset=1' % ( resource.absolute_url(),
                                    form_name,
                                    make_query(**query_kw) )

    # default case, if it's a movement, return link to the explanation of this
    # movement.
241 242 243
    document = self.getObject()
    if document.isMovement():
      explanation = document.getExplanationValue()
244
      if explanation is not None:
245
        return '%s/%s' % (
246 247 248
                self.portal_url.getPortalObject().absolute_url(),
                explanation.getRelativeUrl())
    return ''
Jean-Paul Smets's avatar
Jean-Paul Smets committed
249

250 251
  def getAggregateListText(self):
    aggregate_list = self.Resource_zGetAggregateList(
Jean-Paul Smets's avatar
Jean-Paul Smets committed
252 253 254 255 256 257 258 259
                                   explanation_uid = self.explanation_uid,
                                   node_uid = self.node_uid,
                                   section_uid = self.section_uid,
                                   variation_text = self.variation_text,
                                   resource_uid = self.resource_uid)
    result = []
    for o in aggregate_list:
      result.append(o.relative_url)
260
    return '<br>'.join(result)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
261

262 263 264 265
  def getExplanationText(self):
    # Returns an explanation of the movement
    o = self.getObject()
    if o is not None:
266
      # Get the delivery/order
267 268 269 270 271 272
      if not getattr(o, 'isDelivery', 0):
        delivery = o.getExplanationValue()
      else:
        # Additional inventory movements are catalogged in stock table
        # with the inventory's uid. Then they are their own explanation.
        delivery = o
273
      if delivery is not None:
274 275 276 277
        mapping = {
          'delivery_portal_type' : delivery.getTranslatedPortalType(),
          'delivery_title' : delivery.getTitleOrId()
        }
278 279
        causality = delivery.getCausalityValue()
        if causality is not None:
280
          mapping['causality_portal_type'] = causality.getTranslatedPortalType()
281
          mapping['causality_title'] = causality.getTitleOrId()
282 283 284 285
          return translateString(
            "${delivery_portal_type} ${delivery_title} "
            "(${causality_portal_type} ${causality_title})",
            mapping=mapping)
286
        else :
287 288 289
          return translateString("${delivery_portal_type} ${delivery_title}",
                                 mapping=mapping)
    return translateString('Unknown')
290

291 292 293 294
class TrackingListBrain(InventoryListBrain):
  """
  List of aggregated movements
  """
295
  def getDate(self):
296 297 298 299 300 301 302
    if not self.date:
      return
    # convert the date in the movement's original timezone.
    # This is a somehow heavy operation, but fortunatly it's only called when
    # the brain is accessed from the Shared.DC.ZRDB.Results.Results instance
    obj = self.getObject()
    if obj is not None:
303 304 305 306
      movement = obj.portal_catalog.getObject(self.delivery_uid)
      date = movement.getStartDate() or movement.getStopDate()
      if date is not None:
        timezone = date.timezone()
307 308
        return self.date.toZone(timezone)
    return self.date
309

Jean-Paul Smets's avatar
Jean-Paul Smets committed
310 311 312 313 314 315
class DeliveryListBrain(InventoryListBrain):
  """
    Lists each variation
  """

  # Stock management
Romain Courteaud's avatar
Romain Courteaud committed
316 317
  def getInventory(self, at_date=None, ignore_variation=0, 
                   simulation_state=None, **kw):
318
    if isinstance(simulation_state, str):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
319
      simulation_state = [simulation_state]
320
    where_expression = getattr(self, 'where_expression', None)
Romain Courteaud's avatar
Romain Courteaud committed
321 322 323 324 325 326 327
    result = self.Resource_zGetInventory(
                    resource_uid = [self.resource_uid],
                    to_date=at_date,
                    section_category = self.getPortalDefaultSectionCategory(),
                    variation_text = self.variation_text,
                    simulation_state = simulation_state,
                    where_expression = where_expression)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
328 329 330 331 332 333 334 335 336 337 338 339
    inventory = None
    if len(result) > 0:
      inventory = result[0].inventory
    if inventory is None:
      return 0.0
    else:
      return inventory

  def getAvailableInventory(self):
    """
      Returns current inventory at current date
    """
340
    at_date = DateTime()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
341
    current = self.getCurrentInventory()
342
    result = self.Resource_zGetInventory(
Romain Courteaud's avatar
Romain Courteaud committed
343 344 345 346 347
                resource_uid = [self.resource_uid],
                omit_simulation = 1, omit_input = 1,
                section_category = self.getPortalDefaultSectionCategory(),
                variation_text = self.variation_text,
                simulation_state = self.getPortalReservedInventoryStateList())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
348 349 350 351 352
    reserved_inventory = None
    if len(result) > 0:
      reserved_inventory = result[0].inventory
    if reserved_inventory is None:
      reserved_inventory = 0.0
353 354
    return current + reserved_inventory

355
  def getInventoryAtDate(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
356
    """
357
      Returns inventory at the date provided by the SQL method
Jean-Paul Smets's avatar
Jean-Paul Smets committed
358 359
    """
    at_date=self.at_date
Romain Courteaud's avatar
Romain Courteaud committed
360 361 362 363 364 365
    return self.getInventory(
            at_date=at_date, ignore_variation=0, 
            simulation_state= \
                      list(self.getPortalFutureInventoryStateList()) + \
                      list(self.getPortalReservedInventoryStateList()) + \
                      list(self.getPortalCurrentInventoryStateList()))
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382


class MovementHistoryListBrain(InventoryListBrain):
  """Brain for getMovementHistoryList
  """
  def __init__(self):
    if not self.date:
      return
    # convert the date in the movement's original timezone.
    # This is a somehow heavy operation, but fortunatly it's only called when
    # the brain is accessed from the Shared.DC.ZRDB.Results.Results instance
    obj = self.getObject()
    if obj is not None:
      if self.node_relative_url == obj.getSource():
        timezone = obj.getStartDate().timezone()
      else:
        timezone = obj.getStopDate().timezone()
Jérome Perrin's avatar
Jérome Perrin committed
383
      self.date = self.date.toZone(timezone)
384

385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
  def _debit(self):
    if self.getObject().isCancellationAmount():
      return min(self.total_quantity, 0)
    return max(self.total_quantity, 0)
  debit = ComputedAttribute(_debit, 1)

  def _credit(self):
    if self.getObject().isCancellationAmount():
      return min(-(self.total_quantity or 0), 0)
    return max(-(self.total_quantity or 0), 0)
  credit = ComputedAttribute(_credit, 1)

  def _debit_price(self):
    if self.getObject().isCancellationAmount():
      return min(self.total_price, 0)
    return max(self.total_price, 0)
  debit_price = ComputedAttribute(_debit_price, 1)

  def _credit_price(self):
    if self.getObject().isCancellationAmount():
      return min(-(self.total_price or 0), 0)
    return max(-(self.total_price or 0), 0)
  credit_price = ComputedAttribute(_credit_price, 1)