trytonpsk-stock_co/stock.py

604 lines
21 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from datetime import date, datetime
from trytond.model import fields, ModelView
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, Not, Bool
from trytond.wizard import Wizard, StateView, StateReport, Button
from trytond.report import Report
from trytond.transaction import Transaction
STATES = {'invisible': (Eval('type') != 'goods')}
STATES_MOVE = {
'readonly': Eval('state').in_(['cancel', 'assigned', 'done']),
}
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
description = fields.Char('Description', select=True, states=STATES_MOVE)
current_stock = fields.Function(fields.Float('Current Stock',
depends=['product']), 'on_change_with_current_stock')
@fields.depends('current_stock')
def on_change_product(self, name=None):
super(Move, self).on_change_product()
self.current_stock = self.on_change_with_current_stock()
@fields.depends('product', 'from_location', 'to_location')
def on_change_with_current_stock(self, name=None):
res = 0
location = None
if self.from_location and self.from_location.type == 'storage':
location = self.from_location.parent.storage_location
elif self.to_location and self.to_location.type == 'storage':
location = self.to_location.parent.storage_location
if self.product and location:
context = {
'location_ids': [location.id],
'stock_date_end': date.today(),
}
with Transaction().set_context(context):
res_dict = self.product._get_quantity(
[self.product],
'quantity',
[location.id],
grouping_filter=([self.product.id],)
)
if res_dict.get(self.product.id):
res += res_dict[self.product.id]
return res
class MoveByProductStart(ModelView):
'Move By Product Start'
__name__ = 'stock_co.move_by_product.start'
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
from_location = fields.Many2One('stock.location', 'From Location')
to_location = fields.Many2One('stock.location', 'To Location',
required=True)
categories = fields.Many2Many('product.category', None, None, 'Categories')
company = fields.Many2One('company.company', 'Company',
required=True)
brand = fields.Many2One('product.brand', 'Brand')
party = fields.Many2One('party.party', 'Party')
shipment_draft = fields.Boolean('Shipment Draft')
start_time = fields.Time('Start Time',)
end_time = fields.Time('End Time', states={
'required': Bool(Eval('start_time')),
'invisible': Not(Bool(Eval('start_time'))),
}, depends=['start_time'])
product = fields.Many2One('product.product', 'Product')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_end_date():
Date_ = Pool().get('ir.date')
return Date_.today()
@staticmethod
def default_start_date():
Date_ = Pool().get('ir.date')
return Date_.today()
class PrintMoveByProduct(Wizard):
'Move By Product'
__name__ = 'stock_co.print_move_by_product'
start = StateView('stock_co.move_by_product.start',
'stock_co.print_move_by_product_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('stock_co.move_by_product')
def do_print_(self, action):
category_ids = []
from_location_id = None
brand_id = None
party_id = None
start_time = None
end_time = None
if self.start.from_location:
from_location_id = self.start.from_location.id
if self.start.categories:
category_ids = [acc.id for acc in self.start.categories]
if self.start.brand:
brand_id = self.start.brand.id
if self.start.party:
party_id = self.start.party.id
if self.start.start_time:
start_time = self.start.start_time
if self.start.end_time:
end_time = self.start.end_time
data = {
'company': self.start.company.id,
'from_location': from_location_id,
'to_location': self.start.to_location.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'categories': category_ids,
'brand': brand_id,
'party': party_id,
'shipment_draft': self.start.shipment_draft,
'start_time': start_time,
'end_time': end_time,
'product': self.start.product.id if self.start.product else None
}
return action, data
def transition_print_(self):
return 'end'
class MoveByProduct(Report):
'Move By Product Report'
__name__ = 'stock_co.move_by_product'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Move = pool.get('stock.move')
Location = pool.get('stock.location')
Party = pool.get('party.party')
Brand = pool.get('product.brand')
Category = pool.get('product.template-product.category')
company = Company(data['company'])
from_location = None
brand_name = ''
party_name = ''
start_date = None
end_date = None
categories = Category.search([('category', 'in', data['categories'])])
products_ids = [c.template.id for c in categories]
dom_products = []
if data['start_time']:
start_date = datetime.combine(data['start_date'], data['start_time'])
end_date = datetime.combine(data['end_date'], data['end_time'])
_start_date = Company.convert_timezone(start_date, True)
_end_date = Company.convert_timezone(end_date, True)
dom_products.append(
('to_location.id', '=', data['to_location']),
('create_date', '>=', _start_date),
('create_date', '<=', _end_date)
)
else:
start_date = data['start_date']
end_date = data['end_date']
dom_products.extend([
('to_location', '=', data['to_location']),
['AND',
['OR', [
('planned_date', '>=', start_date),
('planned_date', '<=', end_date),
], [
('effective_date', '>=', start_date),
('effective_date', '<=', end_date),
],
]]
])
if data['shipment_draft']:
dom_products.append(('state', '=', 'draft'))
if data['from_location']:
dom_products.append(
('from_location.id', '=', data['from_location']),
)
from_location = Location(data['from_location'])
if data['categories']:
if products_ids:
dom_products.append(
('product.template', 'in', products_ids)
)
else:
dom_products.append(
('product.template.account_category', 'in', data['categories'])
)
if data['brand']:
dom_products.append(
('product.template.brand', '=', data['brand']),
)
brand_name = Brand(data['brand']).name
if data['party']:
dom_products.append(
('invoice_lines.invoice.party', '=', data['party']),
)
party_name = Party(data['party']).name
if data['product']:
dom_products.append(
('product', '=', data['product']),
)
moves = Move.search([dom_products])
products = {}
for move in moves:
product = move.product
amount = move.quantity * float(product.cost_price)
try:
row = products[product.id]
row[3].append(move.quantity)
row[5].append(amount)
except Exception as e:
template = product.template
brand = template.brand and template.brand.name
category = template.account_category and template.account_category.name
products[product.id] = [
product.code,
product.rec_name,
template.default_uom.symbol,
[move.quantity],
product.cost_price,
[amount],
category,
brand,
template.list_price,
]
report_context['records'] = products.values()
report_context['from_location'] = from_location
report_context['to_location'] = Location(data['to_location'])
report_context['start_date'] = start_date
report_context['end_date'] = end_date
report_context['company'] = company
report_context['brand_name'] = brand_name
report_context['party_name'] = party_name
return report_context
class WarehouseStockStart(ModelView):
'Warehouse Stock Start'
__name__ = 'stock_co.warehouse_stock.start'
company = fields.Many2One('company.company', 'Company', required=True)
locations = fields.Many2Many('stock.location', None, None, "Locations",
domain=[
('type', 'in', ['storage', 'customer']),
('active', '=', True)
], required=True)
to_date = fields.Date('To Date', required=True)
only_minimal_level = fields.Boolean('Only Minimal Level')
group_by_location = fields.Boolean('Group By Location')
category = fields.Many2One('product.category', 'Category')
supplier = fields.Many2One('party.party', 'Supplier')
add_non_existent = fields.Boolean('Add Non-Existent')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_to_date():
Date_ = Pool().get('ir.date')
return Date_.today()
class WarehouseStock(Wizard):
'Warehouse Stock'
__name__ = 'stock_co.warehouse_stock'
start = StateView('stock_co.warehouse_stock.start',
'stock_co.warehouse_stock_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('stock_co.warehouse_stock.report')
def do_print_(self, action):
category_id = None
supplier_id = None
if self.start.category:
category_id = self.start.category.id
if self.start.supplier:
supplier_id = self.start.supplier.id
data = {
'company': self.start.company.id,
'locations': [l.id for l in self.start.locations],
'location_names': ', '.join([l.name for l in self.start.locations]),
'only_minimal_level': self.start.only_minimal_level,
'group_by_location': self.start.group_by_location,
'add_non_existent': self.start.add_non_existent,
'to_date': self.start.to_date,
'category': category_id,
'supplier': supplier_id,
}
return action, data
class WarehouseReport(Report):
'Warehouse Report'
__name__ = 'stock_co.warehouse_stock.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Product = pool.get('product.product')
OrderPoint = pool.get('stock.order_point')
Location = pool.get('stock.location')
ProductsSupplier = pool.get('purchase.product_supplier')
ids_location = data['locations']
locations = Location.browse(data['locations'])
dom_products = [
('active', '=', True),
('template.active', '=', True),
('type', '=', 'goods'),
('consumable', '=', False),
]
stock_context = {
'stock_date_end': data['to_date'],
'locations': ids_location,
}
if data['category']:
dom_products.append(['AND', ['OR', [
('account_category', '=', data['category']),
], [
('categories', 'in', [data['category']]),
],
]])
# dom_products.append(('account_category', '=', data['category']))
if not data['add_non_existent']:
dom_products.append([('quantity', '!=', 0)])
if data['only_minimal_level']:
order_points = OrderPoint.search([
('warehouse_location', 'in', ids_location),
('type', '=', 'purchase'),
])
min_quantities = {op.product.id: op.min_quantity for op in order_points}
products_ids = min_quantities.keys()
dom_products.append(('id', 'in', products_ids))
supplier = None
if data['supplier']:
products_supplier = ProductsSupplier.search([
('party', '=', data['supplier']),
])
supplier = products_supplier[0].party.name
products_ids = [ps.product.id for ps in products_supplier]
dom_products.append(('template', 'in', products_ids))
total_amount = 0
values = {}
products = []
if data['group_by_location']:
for l in locations:
stock_context['locations'] = [l.id]
with Transaction().set_context(stock_context):
prdts = Product.search(dom_products, order=[('code', 'ASC')])
p = [p.id for p in prdts]
products_supplier = ProductsSupplier.search([
('product', 'in', p)])
prod_sup = dict((p.product.id, p.party.name) for p in products_supplier)
total_amount = sum([p.cost_value for p in prdts if p.cost_value])
values[l.id] = {
'name': l.name,
'products': prdts,
'total_amount': total_amount
}
products = values.values()
else:
with Transaction().set_context(stock_context):
products = Product.search(dom_products, order=[('code', 'ASC')])
p = [p.id for p in products]
total_amount = sum([p.cost_value for p in products if p.cost_value])
products_supplier = ProductsSupplier.search([
('product', 'in', p)])
prod_sup = dict((p.product.id, p.party.name) for p in products_supplier)
if data['only_minimal_level']:
products = [p for p in products if p.quantity <= min_quantities[p.id]]
report_context['group_by_location'] = data['group_by_location']
report_context['records'] = products
report_context['prod_sup'] = prod_sup
report_context['total_amount'] = total_amount
report_context['supplier'] = supplier
report_context['location'] = data['location_names']
report_context['stock_date_end'] = data['to_date']
report_context['company'] = Company(data['company'])
return report_context
class PrintProductsStart(ModelView):
'Products Start'
__name__ = 'stock_co.print_products.start'
company = fields.Many2One('company.company', 'Company', required=True)
category = fields.Many2One('product.category', 'Category')
@staticmethod
def default_company():
return Transaction().context.get('company')
class PrintProducts(Wizard):
'Warehouse Stock'
__name__ = 'stock_co.print_products'
start = StateView('stock_co.print_products.start',
'stock_co.print_products_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('stock_co.print_products.report')
def do_print_(self, action):
category_id = None
if self.start.category:
category_id = self.start.category.id
data = {
'company': self.start.company.id,
'category': category_id,
}
return action, data
class PrintProductsReport(Report):
'Warehouse Report'
__name__ = 'stock_co.print_products.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Product = pool.get('product.product')
Date_ = Pool().get('ir.date')
dom_products = [
('active', '=', True),
('type', '=', 'goods'),
('consumable', '=', False),
]
if data['category']:
dom_products.append(('account_category', '=', data['category']))
products = Product.search(dom_products, order=[('code', 'ASC')])
report_context['records'] = products
report_context['stock_date_end'] = Date_.today()
report_context['company'] = Company(data['company'])
return report_context
class Inventory(metaclass=PoolMeta):
__name__ = 'stock.inventory'
@classmethod
def import_data(cls, fields_names, data):
pool = Pool()
InventoryLine = pool.get('stock.inventory.line')
Location = pool.get('stock.location')
Product = pool.get('product.product')
count = 0
head = data[0]
inv_date = head[0]
code = head[1]
year, month, day = inv_date.split('-')
inv_date = date(int(year), int(month), int(day))
locations = Location.search([
('code', '=', code)
])
if not locations:
return count
else:
location_id = locations[0].id
inventory, = cls.create([{
'date': inv_date,
'location': location_id,
}])
lines_to_create = {}
if not len(data[1][1]):
for row in data[1:]:
if not row:
continue
products = Product.search([
('barcode', '=', row[0])
])
if products:
quantity = [r.count(row[0]) for r in data[1:]]
if products[0].id not in lines_to_create.keys():
count += 1
lines_to_create[products[0].id] = {
'inventory': inventory,
'product': products[0].id,
'quantity': sum(quantity),
}
else:
for row in data[1:]:
products = Product.search([
('code', '=', row[0])
])
if products:
count += 1
lines_to_create[products[0].id] = {
'inventory': inventory,
'product': products[0].id,
'quantity': row[1],
}
InventoryLine.create(lines_to_create.values())
return count
class ShipmentInternal(metaclass=PoolMeta):
__name__ = 'stock.shipment.internal'
@classmethod
def import_data(cls, fields_names, data):
pool = Pool()
Move = pool.get('stock.move')
Location = pool.get('stock.location')
Product = pool.get('product.product')
count = 0
head = data[0]
from_ = head[1]
to_ = head[2]
shp_date = head[0]
year, month, day = shp_date.split('-')
date_ = date(int(year), int(month), int(day))
from_locations = Location.search([
('code', '=', from_),
('type', 'in', ['view', 'storage', 'lost_found'])
])
to_locations = Location.search([
('code', '=', to_),
('type', 'in', ['view', 'storage', 'lost_found'])
])
if not from_locations or not to_locations:
return count
else:
from_location_id = from_locations[0].id
to_location_id = to_locations[0].id
shipment, = cls.create([{
'effective_date': date_,
'planned_date': date_,
'planned_start_date': date_,
'from_location': from_location_id,
'to_location': to_location_id,
'state': 'draft',
}])
moves_to_create = []
for row in data[1:]:
count += 1
products = Product.search([
('code', '=', row[0])
])
uom_id = products[0].template.default_uom.id
moves_to_create.append({
'product': products[0].id,
'uom': uom_id,
'quantity': int(row[1]),
'internal_quantity': int(row[1]),
'from_location': from_location_id,
'to_location': to_location_id,
'shipment': str(shipment),
'state': 'draft',
})
Move.create(moves_to_create)
return count