Browse Source

[ADD] Initial Commit

pull/30/merge
SHEREEF PT 8 years ago
parent
commit
4f8a4cef2c
  1. 2
      stock_force_date/__init__.py
  2. 44
      stock_force_date/__manifest__.py
  3. 3
      stock_force_date/models/__init__.py
  4. 272
      stock_force_date/models/stock.py
  5. BIN
      stock_force_date/static/description/banner.jpg
  6. BIN
      stock_force_date/static/description/cybro_logo.png
  7. BIN
      stock_force_date/static/description/history_default.png
  8. BIN
      stock_force_date/static/description/icon.png
  9. 156
      stock_force_date/static/description/index.html
  10. BIN
      stock_force_date/static/description/picking.png
  11. BIN
      stock_force_date/static/description/picking_default.png
  12. BIN
      stock_force_date/static/description/picking_default_2.png
  13. BIN
      stock_force_date/static/description/stock_history.png
  14. BIN
      stock_force_date/static/description/stock_move.png
  15. BIN
      stock_force_date/static/description/stock_move_default.png
  16. 15
      stock_force_date/views/stock_view.xml

2
stock_force_date/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import models

44
stock_force_date/__manifest__.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: fasluca(<https://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Stock Force Date',
'version': '10.0.1.0',
'summary': 'Force Date in Stock Picking',
'description': """
This module will give you a way to record stock picking to a specific date.
this will effect on related stock quants, moves and stock journal entries.
""",
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'website': "https://cybrosys.com/",
'category': 'Warehouse',
'depends': ['stock', 'stock_account'],
'data': [
'views/stock_view.xml',
],
'demo': [],
'images': ['static/description/banner.jpg'],
'license': 'LGPL-3',
'installable': True,
'application': False
}

3
stock_force_date/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import stock

272
stock_force_date/models/stock.py

@ -0,0 +1,272 @@
# -*- coding: utf-8 -*-
import time
import logging
from datetime import datetime
from collections import defaultdict
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
_logger = logging.getLogger(__name__)
class Picking(models.Model):
_inherit = "stock.picking"
force_date = fields.Datetime('Force Date')
class Quant(models.Model):
_inherit = "stock.quant"
@api.model
def quants_move(self, quants, move, location_to, location_from=False, lot_id=False, owner_id=False,
src_package_id=False, dest_package_id=False, entire_pack=False):
"""Moves all given stock.quant in the given destination location. Unreserve from current move.
:param quants: list of tuple(browse record(stock.quant) or None, quantity to move)
:param move: browse record (stock.move)
:param location_to: browse record (stock.location) depicting where the quants have to be moved
:param location_from: optional browse record (stock.location) explaining where the quant has to be taken
(may differ from the move source location in case a removal strategy applied).
This parameter is only used to pass to _quant_create_from_move if a negative quant must be created
:param lot_id: ID of the lot that must be set on the quants to move
:param owner_id: ID of the partner that must own the quants to move
:param src_package_id: ID of the package that contains the quants to move
:param dest_package_id: ID of the package that must be set on the moved quant
"""
# TDE CLEANME: use ids + quantities dict
if location_to.usage == 'view':
raise UserError(_('You cannot move to a location of type view %s.') % (location_to.name))
quants_reconcile_sudo = self.env['stock.quant'].sudo()
quants_move_sudo = self.env['stock.quant'].sudo()
check_lot = False
for quant, qty in quants:
if not quant:
# If quant is None, we will create a quant to move (and potentially a negative counterpart too)
quant = self._quant_create_from_move(
qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id,
dest_package_id=dest_package_id, force_location_from=location_from, force_location_to=location_to)
if move.picking_id.force_date:
quant.write({'in_date': move.picking_id.force_date})
check_lot = True
else:
_logger.info(quant)
quant._quant_split(qty)
_logger.info(quant)
quants_move_sudo |= quant
quants_reconcile_sudo |= quant
if quants_move_sudo:
moves_recompute = quants_move_sudo.filtered(lambda self: self.reservation_id != move).mapped(
'reservation_id')
quants_move_sudo._quant_update_from_move(move, location_to, dest_package_id, lot_id=lot_id,
entire_pack=entire_pack)
moves_recompute.recalculate_move_state()
if location_to.usage == 'internal':
# Do manual search for quant to avoid full table scan (order by id)
self._cr.execute("""
SELECT 0 FROM stock_quant, stock_location WHERE product_id = %s AND stock_location.id = stock_quant.location_id AND
((stock_location.parent_left >= %s AND stock_location.parent_left < %s) OR stock_location.id = %s) AND qty < 0.0 LIMIT 1
""", (move.product_id.id, location_to.parent_left, location_to.parent_right, location_to.id))
if self._cr.fetchone():
quants_reconcile_sudo._quant_reconcile_negative(move)
# In case of serial tracking, check if the product does not exist somewhere internally already
# Checking that a positive quant already exists in an internal location is too restrictive.
# Indeed, if a warehouse is configured with several steps (e.g. "Pick + Pack + Ship") and
# one step is forced (creates a quant of qty = -1.0), it is not possible afterwards to
# correct the inventory unless the product leaves the stock.
picking_type = move.picking_id and move.picking_id.picking_type_id or False
if check_lot and lot_id and move.product_id.tracking == 'serial' and (
not picking_type or (picking_type.use_create_lots or picking_type.use_existing_lots)):
other_quants = self.search([('product_id', '=', move.product_id.id), ('lot_id', '=', lot_id),
('qty', '>', 0.0), ('location_id.usage', '=', 'internal')])
if other_quants:
# We raise an error if:
# - the total quantity is strictly larger than 1.0
# - there are more than one negative quant, to avoid situations where the user would
# force the quantity at several steps of the process
if sum(other_quants.mapped('qty')) > 1.0 or len([q for q in other_quants.mapped('qty') if q < 0]) > 1:
lot_name = self.env['stock.production.lot'].browse(lot_id).name
raise UserError(_('The serial number %s is already in stock.') % lot_name + _(
"Otherwise make sure the right stock/owner is set."))
@api.multi
def _quant_update_from_move(self, move, location_dest_id, dest_package_id, lot_id=False, entire_pack=False):
super(Quant, self)._quant_update_from_move(move, location_dest_id, dest_package_id, lot_id=False,
entire_pack=False)
if move.picking_id.force_date:
self.write({'in_date': move.picking_id.force_date})
def _create_account_move_line(self, move, credit_account_id, debit_account_id, journal_id):
# group quants by cost
quant_cost_qty = defaultdict(lambda: 0.0)
for quant in self:
quant_cost_qty[quant.cost] += quant.qty
AccountMove = self.env['account.move']
for cost, qty in quant_cost_qty.iteritems():
move_lines = move._prepare_account_move_line(qty, cost, credit_account_id, debit_account_id)
if move_lines:
if move.picking_id.force_date:
date = datetime.strptime(move.picking_id.force_date, '%Y-%m-%d %H:%M:%S')
else:
date = self._context.get('force_period_date', fields.Date.context_today(self))
new_account_move = AccountMove.create({
'journal_id': journal_id,
'line_ids': move_lines,
'date': date,
'ref': move.picking_id.name})
new_account_move.post()
class StockMove(models.Model):
_inherit = "stock.move"
@api.multi
def action_done(self):
""" Process completely the moves given and if all moves are done, it will finish the picking. """
self.filtered(lambda move: move.state == 'draft').action_confirm()
Uom = self.env['product.uom']
Quant = self.env['stock.quant']
pickings = self.env['stock.picking']
procurements = self.env['procurement.order']
operations = self.env['stock.pack.operation']
remaining_move_qty = {}
for move in self:
if move.picking_id:
pickings |= move.picking_id
remaining_move_qty[move.id] = move.product_qty
for link in move.linked_move_operation_ids:
operations |= link.operation_id
pickings |= link.operation_id.picking_id
# Sort operations according to entire packages first, then package + lot, package only, lot only
operations = operations.sorted(key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.pack_lot_ids and -1 or 0))
for operation in operations:
# product given: result put immediately in the result package (if False: without package)
# but if pack moved entirely, quants should not be written anything for the destination package
quant_dest_package_id = operation.product_id and operation.result_package_id.id or False
entire_pack = not operation.product_id and True or False
# compute quantities for each lot + check quantities match
lot_quantities = dict((pack_lot.lot_id.id, operation.product_uom_id._compute_quantity(pack_lot.qty, operation.product_id.uom_id)
) for pack_lot in operation.pack_lot_ids)
qty = operation.product_qty
if operation.product_uom_id and operation.product_uom_id != operation.product_id.uom_id:
qty = operation.product_uom_id._compute_quantity(qty, operation.product_id.uom_id)
if operation.pack_lot_ids and float_compare(sum(lot_quantities.values()), qty, precision_rounding=operation.product_id.uom_id.rounding) != 0.0:
raise UserError(_('You have a difference between the quantity on the operation and the quantities specified for the lots. '))
quants_taken = []
false_quants = []
lot_move_qty = {}
prout_move_qty = {}
for link in operation.linked_move_operation_ids:
prout_move_qty[link.move_id] = prout_move_qty.get(link.move_id, 0.0) + link.qty
# Process every move only once for every pack operation
for move in prout_move_qty.keys():
# TDE FIXME: do in batch ?
move.check_tracking(operation)
# TDE FIXME: I bet the message error is wrong
if not remaining_move_qty.get(move.id):
raise UserError(_("The roundings of your unit of measure %s on the move vs. %s on the product don't allow to do these operations or you are not transferring the picking at once. ") % (move.product_uom.name, move.product_id.uom_id.name))
if not operation.pack_lot_ids:
preferred_domain_list = [[('reservation_id', '=', move.id)], [('reservation_id', '=', False)], ['&', ('reservation_id', '!=', move.id), ('reservation_id', '!=', False)]]
quants = Quant.quants_get_preferred_domain(
prout_move_qty[move], move, ops=operation, domain=[('qty', '>', 0)],
preferred_domain_list=preferred_domain_list)
Quant.quants_move(quants, move, operation.location_dest_id, location_from=operation.location_id,
lot_id=False, owner_id=operation.owner_id.id, src_package_id=operation.package_id.id,
dest_package_id=quant_dest_package_id, entire_pack=entire_pack)
else:
# Check what you can do with reserved quants already
qty_on_link = prout_move_qty[move]
rounding = operation.product_id.uom_id.rounding
for reserved_quant in move.reserved_quant_ids:
if (reserved_quant.owner_id.id != operation.owner_id.id) or (reserved_quant.location_id.id != operation.location_id.id) or \
(reserved_quant.package_id.id != operation.package_id.id):
continue
if not reserved_quant.lot_id:
false_quants += [reserved_quant]
elif float_compare(lot_quantities.get(reserved_quant.lot_id.id, 0), 0, precision_rounding=rounding) > 0:
if float_compare(lot_quantities[reserved_quant.lot_id.id], reserved_quant.qty, precision_rounding=rounding) >= 0:
lot_quantities[reserved_quant.lot_id.id] -= reserved_quant.qty
quants_taken += [(reserved_quant, reserved_quant.qty)]
qty_on_link -= reserved_quant.qty
else:
quants_taken += [(reserved_quant, lot_quantities[reserved_quant.lot_id.id])]
lot_quantities[reserved_quant.lot_id.id] = 0
qty_on_link -= lot_quantities[reserved_quant.lot_id.id]
lot_move_qty[move.id] = qty_on_link
remaining_move_qty[move.id] -= prout_move_qty[move]
# Handle lots separately
if operation.pack_lot_ids:
# TDE FIXME: fix call to move_quants_by_lot to ease understanding
self._move_quants_by_lot(operation, lot_quantities, quants_taken, false_quants, lot_move_qty, quant_dest_package_id)
# Handle pack in pack
if not operation.product_id and operation.package_id and operation.result_package_id.id != operation.package_id.parent_id.id:
operation.package_id.sudo().write({'parent_id': operation.result_package_id.id})
# Check for remaining qtys and unreserve/check move_dest_id in
move_dest_ids = set()
for move in self:
if float_compare(remaining_move_qty[move.id], 0, precision_rounding=move.product_id.uom_id.rounding) > 0: # In case no pack operations in picking
move.check_tracking(False) # TDE: do in batch ? redone ? check this
preferred_domain_list = [[('reservation_id', '=', move.id)], [('reservation_id', '=', False)], ['&', ('reservation_id', '!=', move.id), ('reservation_id', '!=', False)]]
quants = Quant.quants_get_preferred_domain(
remaining_move_qty[move.id], move, domain=[('qty', '>', 0)],
preferred_domain_list=preferred_domain_list)
Quant.quants_move(
quants, move, move.location_dest_id,
lot_id=move.restrict_lot_id.id, owner_id=move.restrict_partner_id.id)
# If the move has a destination, add it to the list to reserve
if move.move_dest_id and move.move_dest_id.state in ('waiting', 'confirmed'):
move_dest_ids.add(move.move_dest_id.id)
if move.procurement_id:
procurements |= move.procurement_id
# unreserve the quants and make them available for other operations/moves
move.quants_unreserve()
# Check the packages have been placed in the correct locations
self.mapped('quant_ids').filtered(lambda quant: quant.package_id and quant.qty > 0).mapped('package_id')._check_location_constraint()
# set the move as done
# setting force_date into stock moves
f_date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
for move in self:
if move.picking_id.force_date:
f_date = move.picking_id.force_date
self.write({'state': 'done', 'date': f_date})
# self.write({'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
procurements.check()
# assign destination moves
if move_dest_ids:
# TDE FIXME: record setise me
self.browse(list(move_dest_ids)).action_assign()
pickings.filtered(lambda picking: picking.state == 'done' and not picking.date_done).write(
{'date_done': f_date})
# pickings.filtered(lambda picking: picking.state == 'done' and not picking.date_done).write({'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
return True

BIN
stock_force_date/static/description/banner.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
stock_force_date/static/description/cybro_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
stock_force_date/static/description/history_default.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
stock_force_date/static/description/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

156
stock_force_date/static/description/index.html

@ -0,0 +1,156 @@
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Stock Force Date</h2>
<h4 class="oe_slogan"><a href="https://www.cybrosys.com">Cybrosys Technologies</a></h4>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:#875A7B;">Force a date on stock picking </h2>
<h3 class="oe_slogan">
Help you to give a date on Stock picking, So that you can force Odoo to use the same date on all its related records like, stock move, quants and stock Journal entry.
</h3>
</div>
<div class="oe_row oe_spaced">
<h4>By Default in Odoo,</h4>
</div>
<div class="oe_container">
<div class="oe_row oe_spaced">
<div>
<p class="oe_mt32">
<p>We cannot record a stock transfer happened in a past date with its correct effect.</p>
</p>
</div>
<div>
<div class="oe_demo oe_picture oe_screenshot">
<img src="picking_default.png">
</div>
</div>
</div>
</div>
<div class="oe_container">
<div class="oe_row oe_spaced">
<div>
<p class="oe_mt32">
<p>Even after we change the scheduled date to a past date wont do the job.</p>
</p>
</div>
<div>
<div class="oe_demo oe_picture oe_screenshot">
<img src="picking_default_2.png">
</div>
</div>
</div>
</div>
<div class="oe_container">
<div class="oe_row oe_spaced">
<div>
<p class="oe_mt32">
<p>Stock move is recorded with the current date</p>
</p>
</div>
<div>
<div class="oe_demo oe_picture oe_screenshot">
<img src="stock_move_default.png">
</div>
</div>
</div>
</div>
<div class="oe_container">
<div class="oe_row oe_spaced">
<div>
<p class="oe_mt32">
<p>Stock history is also recorded with the current date</p>
</p>
</div>
<div>
<div class="oe_demo oe_picture oe_screenshot">
<img src="history_default.png">
</div>
</div>
</div>
</div>
<div class="oe_row oe_spaced">
<h4>Using this module,</h4>
</div>
<div class="oe_container">
<div class="oe_row oe_spaced">
<div>
<p class="oe_mt32">
<p>User can give a specific date as 'Force Date' in Stock Picking</p>
</p>
</div>
<div>
<div class="oe_demo oe_picture oe_screenshot">
<img src="picking.png">
</div>
</div>
</div>
</div>
<div class="oe_container">
<div class="oe_row oe_spaced">
<div>
<p class="oe_mt32">
<p>That 'Force Date' will be the date of related stock moves </p>
</p>
</div>
<div>
<div class="oe_demo oe_picture oe_screenshot">
<img src="stock_move.png">
</div>
</div>
</div>
</div>
<div class="oe_container">
<div class="oe_row oe_spaced">
<div>
<p class="oe_mt32">
<p>Stock history is also recorded with that 'Force Date'</p>
</p>
</div>
<div>
<div class="oe_demo oe_picture oe_screenshot">
<img src="stock_history.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<h2 class="oe_slogan" style="margin-top:20px;" >Need Any Help?</h2>
<div class="oe_slogan" style="margin-top:10px !important;">
<div>
<a class="btn btn-primary btn-lg mt8"
style="color: #FFFFFF !important;border-radius: 0;" href="https://www.cybrosys.com"><i
class="fa fa-envelope"></i> Email </a> <a
class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;border-radius: 0;"
href="https://www.cybrosys.com/contact/"><i
class="fa fa-phone"></i> Contact Us </a> <a
class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;border-radius: 0;"
href="https://www.cybrosys.com/odoo-customization-and-installation/"><i
class="fa fa-check-square"></i> Request Customization </a>
</div>
<br>
<img src="cybro_logo.png" style="width: 190px; margin-bottom: 20px;" class="center-block">
<div>
<a href="https://twitter.com/cybrosys" target="_blank"><i class="fa fa-2x fa-twitter" style="color:white;background: #00a0d1;width:35px;"></i></a></td>
<a href="https://www.linkedin.com/company/cybrosys-technologies-pvt-ltd" target="_blank"><i class="fa fa-2x fa-linkedin" style="color:white;background: #31a3d6;width:35px;padding-left: 3px;"></i></a></td>
<a href="https://www.facebook.com/cybrosystechnologies" target="_blank"><i class="fa fa-2x fa-facebook" style="color:white;background: #3b5998;width:35px;padding-left: 8px;"></i></a></td>
<a href="https://plus.google.com/106641282743045431892/about" target="_blank"><i class="fa fa-2x fa-google-plus" style="color:white;background: #c53c2c;width:35px;padding-left: 3px;"></i></a></td>
<a href="https://in.pinterest.com/cybrosys" target="_blank"><i class="fa fa-2x fa-pinterest" style="color:white;background: #ac0f18;width:35px;padding-left: 3px;"></i></a></td>
</div>
</div>
</section>

BIN
stock_force_date/static/description/picking.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
stock_force_date/static/description/picking_default.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
stock_force_date/static/description/picking_default_2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
stock_force_date/static/description/stock_history.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
stock_force_date/static/description/stock_move.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
stock_force_date/static/description/stock_move_default.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

15
stock_force_date/views/stock_view.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="az_view_picking_form" model="ir.ui.view">
<field name="name">az.stock.picking.form</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<field name="min_date" position="before">
<field name="force_date"/>
</field>
</field>
</record>
</data>
</odoo>
Loading…
Cancel
Save