604 lines
21 KiB
Python
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
|