630 lines
21 KiB
Python
630 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
|
|
from sql import Table
|
|
from trytond.model import Workflow, ModelView, ModelSQL, fields
|
|
from trytond.report import Report
|
|
from trytond.pool import Pool
|
|
from trytond.pyson import Eval
|
|
from trytond.modules.company import CompanyReport
|
|
from trytond.transaction import Transaction
|
|
from trytond.wizard import Wizard, StateReport, StateView, Button
|
|
from decimal import Decimal
|
|
|
|
|
|
class QualityAnalysis(ModelSQL, ModelView):
|
|
"Quality Analysis"
|
|
__name__ = "farming.quality.analysis"
|
|
origin = fields.Reference('Origin', selection='get_origin', select=True,
|
|
depends=['state'])
|
|
test = fields.Many2One('farming.quality.test', 'Test', required=True)
|
|
issue_date = fields.Date('Issue Date', required=True)
|
|
returned_qty = fields.Float('Returned Qty', required=True)
|
|
farm = fields.Char('Farm')
|
|
action = fields.Char('Action')
|
|
|
|
@staticmethod
|
|
def default_issue_date():
|
|
return date.today()
|
|
|
|
@classmethod
|
|
def _get_origin(cls):
|
|
'Return list of Model names for origin Reference'
|
|
return ['purchase.line', 'stock.move', 'sale.line']
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
Model = Pool().get('ir.model')
|
|
get_name = Model.get_name
|
|
models = cls._get_origin()
|
|
return [(None, '')] + [(m, get_name(m)) for m in models]
|
|
|
|
|
|
class QualityTest(ModelSQL, ModelView):
|
|
"Quality Test"
|
|
__name__ = "farming.quality.test"
|
|
name = fields.Char('Name', required=True)
|
|
kind = fields.Selection([
|
|
('quality', 'Quality'),
|
|
('phytosanitary', 'Phytosanitary')
|
|
], 'Kind', required=True)
|
|
kind_string = kind.translated('kind')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(QualityTest, cls).__setup__()
|
|
cls._order.insert(0, ('name', 'ASC'))
|
|
|
|
|
|
class ProductSpecies(ModelSQL, ModelView):
|
|
"Product Species"
|
|
__name__ = "farming.product.species"
|
|
name = fields.Char('Name', required=True)
|
|
area = fields.Float('Area')
|
|
certificate = fields.Many2One('farming.quality.ica', 'ICA', required=True)
|
|
|
|
|
|
class ICACertificate(ModelSQL, ModelView):
|
|
"ICA Certificate"
|
|
__name__ = "farming.quality.ica"
|
|
number = fields.Char('Number', required=True)
|
|
issue_date = fields.Date('Issue Date', required=True)
|
|
expiration_date = fields.Date('Expiration Date', required=True)
|
|
party = fields.Many2One('party.party', 'Party', required=True)
|
|
species = fields.One2Many('farming.product.species', 'certificate',
|
|
'Species')
|
|
farm = fields.Char('Farm', required=True)
|
|
location = fields.Char('Location', required=True)
|
|
product = fields.Many2One('product.template', 'Product', required=True,
|
|
domain=[('type', '=', 'goods')])
|
|
code = fields.Char('Code')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ICACertificate, cls).__setup__()
|
|
cls._order.insert(0, ('expiration_date', 'ASC'))
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
if clause[1].startswith('!') or clause[1].startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
return [
|
|
bool_op,
|
|
('number',) + tuple(clause[1:]),
|
|
('farm',) + tuple(clause[1:]),
|
|
('party.name',) + tuple(clause[1:]),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
return '[' + self.number + '] ' + self.farm
|
|
|
|
|
|
class Phytosanitary(ModelSQL, ModelView):
|
|
"Phytosanitary"
|
|
__name__ = "farming.phyto"
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', required=True, select=True)
|
|
ica = fields.Many2One('farming.quality.ica', 'ICA Certificate',
|
|
required=True, select=True)
|
|
issue_date = fields.Date('Issue Date', required=True)
|
|
stock_lots = fields.One2Many('stock.lot', 'phyto', 'Stock Lots')
|
|
balance = fields.Function(fields.Integer('Balance'), 'get_balance')
|
|
state = fields.Selection([
|
|
('active', 'Active'),
|
|
('finished', 'Finished'),
|
|
], 'State', states={'readonly': True}, select=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Phytosanitary, cls).__setup__()
|
|
cls._buttons.update({
|
|
'refresh': {
|
|
'depends': ['state'],
|
|
}
|
|
})
|
|
|
|
@staticmethod
|
|
def default_issue_date():
|
|
return date.today()
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'active'
|
|
|
|
def get_balance(self, name=None):
|
|
res = [stock.balance for stock in self.stock_lots if stock.balance]
|
|
return Decimal(sum(res))
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
if clause[1].startswith('!') or clause[1].startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
return [
|
|
bool_op,
|
|
('number',) + tuple(clause[1:]),
|
|
('ica.party.name',) + tuple(clause[1:]),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
return '[' + self.number + '] ' + self.ica.party.name
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def refresh(cls, records):
|
|
for rec in records:
|
|
if rec.balance == 0:
|
|
rec.state = 'finished'
|
|
else:
|
|
rec.state = 'active'
|
|
rec.save()
|
|
|
|
|
|
class PhytoStock(ModelSQL, ModelView):
|
|
"Phytosanitary Stock"
|
|
__name__ = "farming.phyto.stock"
|
|
phyto = fields.Many2One('farming.phyto', 'Phyto', required=True,
|
|
ondelete='CASCADE')
|
|
lot = fields.Many2One('stock.lot', 'Lot')
|
|
product = fields.Many2One('product.product', 'Product', required=True,
|
|
domain=[('type', '=', 'goods')])
|
|
location = fields.Char('Location')
|
|
balance = fields.Integer('Balance', states={'readonly': True})
|
|
moves = fields.One2Many('farming.phyto.stock.move', 'stock', 'Moves')
|
|
state = fields.Selection([
|
|
('active', 'Active'),
|
|
('finished', 'Finished'),
|
|
], 'State', states={'readonly': True}, select=True)
|
|
|
|
@staticmethod
|
|
def default_balance():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'active'
|
|
|
|
@fields.depends('balance', 'moves', 'state')
|
|
def on_change_moves(self, name=None):
|
|
res = []
|
|
for mo in self.moves:
|
|
res.append(mo.move_in - mo.move_out)
|
|
val = sum(res)
|
|
self.balance = val
|
|
if len(self.moves) > 1 and val == 0:
|
|
self.state = 'finished'
|
|
else:
|
|
self.state = 'active'
|
|
|
|
|
|
class PhytoStockMove(ModelSQL, ModelView):
|
|
"Phytosanitary Stock Move"
|
|
__name__ = "farming.phyto.stock.move"
|
|
stock = fields.Many2One('farming.phyto.stock', 'Stock Phyto',
|
|
required=True, ondelete='CASCADE')
|
|
date = fields.Date('Date', required=True)
|
|
move_in = fields.Integer('Move In', required=True)
|
|
move_out = fields.Integer('Move Out', required=True)
|
|
move = fields.Many2One('stock.move', 'Move', required=False)
|
|
origin = fields.Reference('Origin', selection='get_origin', select=True)
|
|
|
|
@staticmethod
|
|
def default_move_in():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_move_out():
|
|
return 0
|
|
|
|
@classmethod
|
|
def delete(cls, records):
|
|
stocks = [rec.stock for rec in records]
|
|
super(PhytoStockMove, cls).delete(records)
|
|
for sto in stocks:
|
|
sto.on_change_moves()
|
|
sto.save()
|
|
|
|
@classmethod
|
|
def _get_origin(cls):
|
|
'Return list of Model names for origin Reference'
|
|
return ['stock.move', 'exportation.phyto.line']
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
Model = Pool().get('ir.model')
|
|
get_name = Model.get_name
|
|
models = cls._get_origin()
|
|
return [(None, '')] + [(m, get_name(m)) for m in models]
|
|
|
|
|
|
class ExportationPhyto(Workflow, ModelSQL, ModelView):
|
|
"Exportation Phytosanitary"
|
|
__name__ = 'exportation.phyto'
|
|
STATES = {
|
|
'readonly': (Eval('state') != 'draft'),
|
|
}
|
|
date = fields.Date('Date', required=True, states=STATES)
|
|
number = fields.Char('Number', readonly=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
states=STATES)
|
|
customer = fields.Many2One('party.party', 'Customer', select=True,
|
|
states=STATES)
|
|
in_port = fields.Many2One('exportation.port', 'In Port', select=True)
|
|
out_port = fields.Many2One('exportation.port', 'Out Port', select=True)
|
|
freight_forwader = fields.Many2One('party.party', 'Freight Forwader',
|
|
required=True, select=True)
|
|
specie = fields.Many2One('product.template', 'Specie', required=True,
|
|
states=STATES, domain=[
|
|
('type', '=', 'goods'),
|
|
('farming', '=', True),
|
|
])
|
|
lines = fields.One2Many('exportation.phyto.line', 'phyto', 'Lines',
|
|
states=STATES)
|
|
phyto_moves = fields.One2Many("stock.lot.phyto.move", 'exportation_phyto',"Move Phyto")
|
|
dispatched_date = fields.Date('Dispatched Date', required=True,
|
|
states=STATES)
|
|
technical_prof = fields.Many2One('party.party',
|
|
'Technical Professional', states=STATES)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('confirm', 'Confirm'),
|
|
('cancel', 'Canceled'),
|
|
], 'State', readonly=True, select=True)
|
|
expire_start_date = fields.Date('Expire Start Date', required=True,
|
|
states=STATES)
|
|
expire_end_date = fields.Date('Expire End Date', required=True,
|
|
states=STATES)
|
|
total_quantity = fields.Function(fields.Numeric('Total Quantity',
|
|
digits=(16, 2)), 'get_total_quantity')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ExportationPhyto, cls).__setup__()
|
|
cls._order.insert(0, ('date', 'DESC'))
|
|
cls._transitions |= set((
|
|
('draft', 'confirm'),
|
|
('confirm', 'draft'),
|
|
('cancel', 'draft'),
|
|
('draft', 'cancel'),
|
|
))
|
|
cls._buttons.update({
|
|
'cancel': {
|
|
'invisible': Eval('state') != 'draft'
|
|
},
|
|
'draft': {
|
|
'invisible': Eval('state') == 'draft',
|
|
},
|
|
'confirm': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'select_phytos': {
|
|
'invisible': (Eval('state') != 'draft') | Eval('lines', []),
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company') or False
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def select_phytos(cls, records):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
Production = pool.get('production')
|
|
ExLine = pool.get('exportation.phyto.line')
|
|
MovePhyto = pool.get('stock.lot.phyto.move')
|
|
move_phyto_table = Table('stock_lot_phyto_move')
|
|
# productions = []
|
|
id_export_pytho = None
|
|
Lot = pool.get('stock.lot')
|
|
for ex_phyto in records:
|
|
moves_phyto = []
|
|
moves = []
|
|
lots = set()
|
|
export_date = ex_phyto.dispatched_date
|
|
productions = Production.search([
|
|
('customer', '=', ex_phyto.customer.id),
|
|
('state', 'in', ('done', 'running', 'assigned')),
|
|
('delivery_date', '=', ex_phyto.dispatched_date),
|
|
])
|
|
id_export_pytho = ex_phyto.id
|
|
for pcc in productions:
|
|
for input in pcc.inputs:
|
|
if input.product.template.farming:
|
|
moves.append(input)
|
|
for move in moves:
|
|
query = move_phyto_table.select(
|
|
move_phyto_table.id,
|
|
where=move_phyto_table.origin == str(move))
|
|
cursor.execute(*query)
|
|
result = cursor.fetchall()
|
|
if result:
|
|
for move_phyto_id in result:
|
|
m_phyto = MovePhyto.search([
|
|
('id', '=', move_phyto_id[0])
|
|
])
|
|
moves_phyto.append(m_phyto[0])
|
|
|
|
for move_phyto in moves_phyto:
|
|
if move_phyto.move_in > 0 :
|
|
lots.add(move_phyto.lot)
|
|
ex_line = {
|
|
'phyto': id_export_pytho,
|
|
'ica_register': move_phyto.lot.phyto.ica.id,
|
|
}
|
|
line, = ExLine.create([ex_line])
|
|
new_phyto_move = {
|
|
'lot': move_phyto.lot,
|
|
'date_move': export_date,
|
|
'move_in': 0,
|
|
'move_out': move_phyto.move_in,
|
|
'origin': str(line)
|
|
}
|
|
new_id_phyto, = MovePhyto.create([new_phyto_move])
|
|
line.phyto_moves = new_id_phyto.id
|
|
line.quantity = int(move_phyto.move_in)
|
|
line.save()
|
|
if lots:
|
|
Lot.recompute_balance(lots)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancel')
|
|
def cancel(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('confirm')
|
|
def confirm(cls, records):
|
|
for rec in records:
|
|
rec.set_number()
|
|
|
|
def set_number(self):
|
|
"""
|
|
Fill the number field with the booking sequence
|
|
"""
|
|
pool = Pool()
|
|
Config = pool.get('farming.configuration')
|
|
config = Config.get_config()
|
|
|
|
if self.number or not config.phyto_export_sequence:
|
|
return
|
|
number = config.phyto_export_sequence.get()
|
|
self.write([self], {'number': number})
|
|
|
|
def get_total_quantity(self, name=None):
|
|
return sum(line.quantity for line in self.lines)
|
|
|
|
|
|
class ExportationPhytoLine(ModelSQL, ModelView):
|
|
"Exportation Phyto Line"
|
|
__name__ = 'exportation.phyto.line'
|
|
ica_register = fields.Many2One('farming.quality.ica', 'ICA Register',
|
|
required=True)
|
|
phyto = fields.Many2One('exportation.phyto', 'Phyto')
|
|
quantity = fields.Integer('Quantity')
|
|
phyto_moves = fields.Many2One('stock.lot.phyto.move', 'origin', 'Phyto Move',
|
|
domain=[('lot.phyto.ica', '=', Eval('ica_register'))], ondelete="CASCADE")
|
|
manual = fields.Boolean('Manual')
|
|
|
|
@classmethod
|
|
def delete(cls, records):
|
|
StockMove = Pool().get('stock.lot.phyto.move')
|
|
for rec in records:
|
|
if rec.phyto_moves:
|
|
StockMove.delete([rec.phyto_moves])
|
|
super(ExportationPhytoLine, cls).delete(records)
|
|
|
|
|
|
class ExportationPhytosanitaryReport(CompanyReport):
|
|
__name__ = 'exportation.phyto'
|
|
|
|
|
|
class PhytoMovesStart(ModelView):
|
|
'Phyto Moves Start'
|
|
__name__ = 'farming.phyto_moves.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
date_ = fields.Date("Date", required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class PhytoMoves(Wizard):
|
|
'Phyto Moves'
|
|
__name__ = 'farming.phyto_moves'
|
|
start = StateView(
|
|
'farming.phyto_moves.start',
|
|
'farming.phyto_moves_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateReport('farming.phyto_moves.report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'date': self.start.date_,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class PhytoMovesReport(Report):
|
|
'Phyto Moves Report'
|
|
__name__ = 'farming.phyto_moves.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
company_id = Transaction().context.get('company')
|
|
Company = pool.get('company.company')
|
|
Phyto = pool.get('farming.phyto')
|
|
phytos = Phyto.search([
|
|
('issue_date', '=', data['date']),
|
|
], order=[('issue_date', 'ASC')]
|
|
)
|
|
records_ = []
|
|
_append = records_.append
|
|
for ph in phytos:
|
|
for st in ph.stock_lots:
|
|
balance = st.quantity_purchase
|
|
rec_in = {
|
|
'doc': ph.number,
|
|
'supplier': ph.ica.farm,
|
|
'date': ph.issue_date,
|
|
'reference': ph.ica.product.name,
|
|
'in': balance,
|
|
'out': 0,
|
|
'dev': '',
|
|
'balance': balance,
|
|
'customer': '',
|
|
}
|
|
_append(rec_in)
|
|
for mv in st.phyto_move:
|
|
if mv.move_out > 0:
|
|
balance -= mv.move_out
|
|
customer = ''
|
|
doc = ''
|
|
date_ = ''
|
|
if mv.origin and mv.origin.__name__ == 'exportation.phyto.line':
|
|
phyto = mv.origin.phyto
|
|
customer = phyto.customer.name
|
|
doc = phyto.number
|
|
date_ = phyto.dispatched_date
|
|
rec_out = {
|
|
'doc': doc,
|
|
'supplier': '',
|
|
'date': date_,
|
|
'reference': '',
|
|
'in': '',
|
|
'out': mv.move_out,
|
|
'dev': '',
|
|
'balance': balance,
|
|
'customer': customer,
|
|
}
|
|
_append(rec_out)
|
|
report_context['records'] = records_
|
|
report_context['company'] = Company(company_id)
|
|
return report_context
|
|
|
|
|
|
class PurchaseMonitoringStart(ModelView):
|
|
'Purchase Monitoring Start'
|
|
__name__ = 'farming.purchase_monitoring.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
start_date = fields.Date("Start Date", required=True)
|
|
end_date = fields.Date("End Date", required=True)
|
|
returned_included = fields.Boolean("Return Included")
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class PurchaseMonitoring(Wizard):
|
|
'Purchase Monitoring'
|
|
__name__ = 'farming.purchase_monitoring'
|
|
start = StateView(
|
|
'farming.purchase_monitoring.start',
|
|
'farming.purchase_monitoring_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateReport('farming.purchase_monitoring.report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'returned_included': self.start.returned_included,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class PurchaseMonitoringReport(Report):
|
|
'Purchase Monitoring Report'
|
|
__name__ = 'farming.purchase_monitoring.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
company_id = Transaction().context.get('company')
|
|
Company = pool.get('company.company')
|
|
Purchase = pool.get('purchase.purchase')
|
|
purchases = Purchase.search([
|
|
('delivery_date', '>=', data['start_date']),
|
|
('delivery_date', '<=', data['end_date']),
|
|
('ica_certicate', '!=', None),
|
|
('state', 'in', ['confirmed', 'processing', 'done']),
|
|
], order=[('delivery_date', 'ASC')]
|
|
)
|
|
|
|
records_ = []
|
|
_append = records_.append
|
|
for purc in purchases:
|
|
phyto = ''
|
|
for ship in purc.shipments:
|
|
if ship.phyto:
|
|
phyto = ship.phyto.number
|
|
if not phyto and not data['returned_included']:
|
|
continue
|
|
for line in purc.lines:
|
|
rec_in = {
|
|
'date': purc.delivery_date,
|
|
'supplier': purc.ica_certicate.farm,
|
|
'doc': phyto,
|
|
'reference': line.product.template.name,
|
|
'quantity': line.original_qty,
|
|
'returned_qty': line.returned_qty,
|
|
'qty_checked': line.qty_checked,
|
|
'analysis': [],
|
|
'action': '',
|
|
}
|
|
_analysis = []
|
|
for qa in line.quality_analysis:
|
|
_analysis.append({
|
|
'returned_qty': qa.returned_qty,
|
|
'test_kind': qa.test.kind_string,
|
|
'test': qa.test.name,
|
|
'action': qa.action,
|
|
})
|
|
if _analysis:
|
|
rec_in['analysis'] = _analysis
|
|
else:
|
|
rec_in['action'] = 'NO PLAGA'
|
|
|
|
_append(rec_in)
|
|
report_context['records'] = records_
|
|
report_context['company'] = Company(company_id)
|
|
return report_context
|