[ADD] Invoice qty

This commit is contained in:
Dario Lodeiros
2019-01-17 23:21:53 +01:00
parent 45ebbdc4a9
commit c5d3f7facc
7 changed files with 60 additions and 56 deletions

View File

@@ -82,31 +82,22 @@ class HotelReservation(models.Model):
def _compute_invoice_status(self): def _compute_invoice_status(self):
""" """
Compute the invoice status of a Reservation. Possible statuses: Compute the invoice status of a Reservation. Possible statuses:
- no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to - no: if the Folio is not in status 'sale' or 'done', we consider that there is nothing to
invoice. This is also hte default value if the conditions of no other status is met. invoice. This is also hte default value if the conditions of no other status is met.
- to invoice: we refer to the quantity to invoice of the line. Refer to method - to invoice: we refer to the quantity to invoice of the line. Refer to method
`_get_to_invoice_qty()` for more information on how this quantity is calculated. `_get_to_invoice_qty()` for more information on how this quantity is calculated.
- upselling: this is possible only for a product invoiced on ordered quantities for which
we delivered more than expected. The could arise if, for example, a project took more
time than expected but we decided not to invoice the extra cost to the client. This
occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity
is removed from the list.
- invoiced: the quantity invoiced is larger or equal to the quantity ordered. - invoiced: the quantity invoiced is larger or equal to the quantity ordered.
""" """
pass precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
#~ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') for line in self:
#~ for line in self: if line.state in ('draft'):
#~ if line.state not in ('confirm', 'done'): line.invoice_status = 'no'
#~ line.invoice_status = 'no' elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
#~ elif not float_is_zero(line.qty_to_invoice, precision_digits=precision): line.invoice_status = 'to invoice'
#~ line.invoice_status = 'to invoice' elif float_compare(line.qty_invoiced, len(line.reservation_line_ids), precision_digits=precision) >= 0:
#~ elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\ line.invoice_status = 'invoiced'
#~ float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1: else:
#~ line.invoice_status = 'upselling' line.invoice_status = 'no'
#~ elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0:
#~ line.invoice_status = 'invoiced'
#~ else:
#~ line.invoice_status = 'no'
@api.model @api.model
@@ -269,7 +260,6 @@ class HotelReservation(models.Model):
related='pricelist_id.currency_id', related='pricelist_id.currency_id',
string='Currency', readonly=True, required=True) string='Currency', readonly=True, required=True)
invoice_status = fields.Selection([ invoice_status = fields.Selection([
('upselling', 'Upselling Opportunity'),
('invoiced', 'Fully Invoiced'), ('invoiced', 'Fully Invoiced'),
('to invoice', 'To Invoice'), ('to invoice', 'To Invoice'),
('no', 'Nothing to Invoice') ('no', 'Nothing to Invoice')
@@ -283,7 +273,7 @@ class HotelReservation(models.Model):
qty_invoiced = fields.Float( qty_invoiced = fields.Float(
compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True, compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
digits=dp.get_precision('Product Unit of Measure')) digits=dp.get_precision('Product Unit of Measure'))
invoice_line_ids = fields.Many2many('account.invoice.line', 'reservation_line_invoice_rel', 'reservation_id', 'invoice_line_id', string='Invoice Lines', copy=False) invoice_line_ids = fields.Many2many('account.invoice.line', 'reservation_invoice_rel', 'reservation_id', 'invoice_line_id', string='Invoice Lines', copy=False)
# qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0) # qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0)
# qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True) # qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
price_subtotal = fields.Monetary(string='Subtotal', price_subtotal = fields.Monetary(string='Subtotal',
@@ -327,7 +317,7 @@ class HotelReservation(models.Model):
vals.update({'folio_id': folio.id, vals.update({'folio_id': folio.id,
'reservation_type': vals.get('reservation_type'), 'reservation_type': vals.get('reservation_type'),
'channel_type': vals.get('channel_type')}) 'channel_type': vals.get('channel_type')})
if 'service_ids' in vals: if 'service_ids' in vals and vals['service_ids'][0][2]:
for service in vals['service_ids']: for service in vals['service_ids']:
service[2]['folio_id'] = folio.id service[2]['folio_id'] = folio.id
vals.update({ vals.update({
@@ -1043,7 +1033,7 @@ class HotelReservation(models.Model):
'ignore_avail_restrictions': True}).create(vals) 'ignore_avail_restrictions': True}).create(vals)
if not reservation_copy: if not reservation_copy:
raise ValidationError(_("Unexpected error copying record. \ raise ValidationError(_("Unexpected error copying record. \
Can't split reservation!")) Can't split reservation!"))
record.write({ record.write({
'checkout': new_start_date_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'checkout': new_start_date_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'price_total': tprice[0], 'price_total': tprice[0],
@@ -1189,31 +1179,22 @@ class HotelReservation(models.Model):
Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is
calculated from the ordered quantity. Otherwise, the quantity delivered is used. calculated from the ordered quantity. Otherwise, the quantity delivered is used.
""" """
pass for line in self:
#~ for line in self: if line.folio_id.state not in ['draft']:
#~ if line.folio_id.state in ['confirm', 'done']: line.qty_to_invoice = len(line.reservation_line_ids) - line.qty_invoiced
#~ if line.room_type_id.product_id.invoice_policy == 'order': else:
#~ line.qty_to_invoice = line.nights - line.qty_invoiced line.qty_to_invoice = 0
#~ else:
#~ line.qty_to_invoice = line.qty_delivered - line.qty_invoiced
#~ else:
#~ line.qty_to_invoice = 0
@api.depends('invoice_line_ids.invoice_id.state', 'invoice_line_ids.quantity') @api.depends('invoice_line_ids.invoice_id.state', 'invoice_line_ids.quantity')
def _get_invoice_qty(self): def _get_invoice_qty(self):
""" """
Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. Note Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. We
that this is the case only if the refund is generated from the SO and that is intentional: if must check day per day and sum or decreased on 1 unit per invoice_line
a refund made would automatically decrease the invoiced quantity, then there is a risk of reinvoicing
it automatically, which may not be wanted at all. That's why the refund has to be created from the SO
""" """
pass for line in self:
#~ for line in self: qty_invoiced = 0.0
#~ qty_invoiced = 0.0 for day in line.reservation_line_ids:
#~ for invoice_line in line.invoice_line_ids: invoice_lines = day.invoice_line_ids.filtered(lambda r: r.invoice_id.state != 'cancel')
#~ if invoice_line.invoice_id.state != 'cancel': qty_invoiced += len(invoice_lines.filtered(lambda r: r.invoice_id.type == 'out_invoice')) - \
#~ if invoice_line.invoice_id.type == 'out_invoice': len(invoice_lines.filtered(lambda r: r.invoice_id.type == 'out_refund'))
#~ qty_invoiced += invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_uom) line.qty_invoiced = qty_invoiced
#~ elif invoice_line.invoice_id.type == 'out_refund':
#~ qty_invoiced -= invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_uom)
#~ line.qty_invoiced = qty_invoiced

View File

@@ -17,8 +17,11 @@ class HotelReservationLine(models.Model):
discount = fields.Float( discount = fields.Float(
string='Discount (%)', string='Discount (%)',
digits=dp.get_precision('Discount'), default=0.0) digits=dp.get_precision('Discount'), default=0.0)
invoiced = fields.Boolean('Invoiced') invoice_line_ids = fields.Many2many(
'account.invoice.line',
'reservation_line_invoice_rel',
'reservation_line_id', 'invoice_line_id',
string='Invoice Lines', readonly=True, copy=False)
@api.constrains('date') @api.constrains('date')
def constrains_duplicated_date(self): def constrains_duplicated_date(self):

View File

@@ -135,7 +135,6 @@ class HotelService(models.Model):
product_image = fields.Binary('Product Image', related="product_id.image", store=False) product_image = fields.Binary('Product Image', related="product_id.image", store=False)
company_id = fields.Many2one(related='folio_id.company_id', string='Company', store=True, readonly=True) company_id = fields.Many2one(related='folio_id.company_id', string='Company', store=True, readonly=True)
invoice_status = fields.Selection([ invoice_status = fields.Selection([
('upselling', 'Upselling Opportunity'),
('invoiced', 'Fully Invoiced'), ('invoiced', 'Fully Invoiced'),
('to invoice', 'To Invoice'), ('to invoice', 'To Invoice'),
('no', 'Nothing to Invoice') ('no', 'Nothing to Invoice')

View File

@@ -8,7 +8,7 @@ class AccountInvoiceLine(models.Model):
reservation_ids = fields.Many2many( reservation_ids = fields.Many2many(
'hotel.reservation', 'hotel.reservation',
'reservation_line_invoice_rel', 'reservation_invoice_rel',
'invoice_line_id', 'reservation_id', 'invoice_line_id', 'reservation_id',
string='Reservations', readonly=True, copy=False) string='Reservations', readonly=True, copy=False)
service_ids = fields.Many2many( service_ids = fields.Many2many(
@@ -16,3 +16,9 @@ class AccountInvoiceLine(models.Model):
'service_line_invoice_rel', 'service_line_invoice_rel',
'invoice_line_id', 'service_id', 'invoice_line_id', 'service_id',
string='Services', readonly=True, copy=False) string='Services', readonly=True, copy=False)
reservation_line_ids = fields.Many2many(
'hotel.reservation.line',
'reservation_line_invoice_rel',
'invoice_line_id', 'reservation_line_id',
string='Reservation Lines', readonly=True, copy=False)

View File

@@ -9,6 +9,9 @@
<field name="folio_ids" widget="many2many_tags"/> <field name="folio_ids" widget="many2many_tags"/>
<field name="from_folio" invisible="1" /> <field name="from_folio" invisible="1" />
</xpath> </xpath>
<xpath expr="//field[@name='product_id']" position="after">
<field name="reservation_line_ids" widget="many2many_tags"/>
</xpath>
<xpath expr="//button[@name='%(account.action_account_invoice_payment)d']" position="attributes"> <xpath expr="//button[@name='%(account.action_account_invoice_payment)d']" position="attributes">
<attribute name="attrs">{'invisible': ['|',('from_folio','=',True)]}</attribute> <attribute name="attrs">{'invisible': ['|',('from_folio','=',True)]}</attribute>
</xpath> </xpath>

View File

@@ -317,8 +317,10 @@ class FolioAdvancePaymentInv(models.TransientModel):
'qty': 1, 'qty': 1,
'discount': day.discount, 'discount': day.discount,
'price_unit': day.price + extra_price, 'price_unit': day.price + extra_price,
'date_to': day.date 'date_to': day.date,
'reservation_line_ids': []
} }
invoice_lines[group_key][('reservation_line_ids')].append((4,day.id))
for group_key in invoice_lines: for group_key in invoice_lines:
vals.append((0, False, invoice_lines[group_key])) vals.append((0, False, invoice_lines[group_key]))
self.line_ids = vals self.line_ids = vals
@@ -394,6 +396,9 @@ class LineAdvancePaymentInv(models.TransientModel):
reservation_id = fields.Many2one('hotel.reservation') reservation_id = fields.Many2one('hotel.reservation')
service_id = fields.Many2one('hotel.service') service_id = fields.Many2one('hotel.service')
folio_id = fields.Many2one('hotel.folio', compute='_compute_folio_id') folio_id = fields.Many2one('hotel.folio', compute='_compute_folio_id')
reservation_line_ids = fields.Many2many(
'hotel.reservation.line',
string='Reservation Lines')
def _compute_folio_id(self): def _compute_folio_id(self):
for record in self: for record in self:
@@ -421,7 +426,6 @@ class LineAdvancePaymentInv(models.TransientModel):
fpos = line.folio_id.fiscal_position_id or line.folio_id.partner_id.property_account_position_id fpos = line.folio_id.fiscal_position_id or line.folio_id.partner_id.property_account_position_id
if fpos: if fpos:
account = fpos.map_account(account) account = fpos.map_account(account)
vals = { vals = {
'name': line.description, 'name': line.description,
'sequence': origin.sequence, 'sequence': origin.sequence,
@@ -434,12 +438,19 @@ class LineAdvancePaymentInv(models.TransientModel):
'product_id': product.id or False, 'product_id': product.id or False,
'invoice_line_tax_ids': [(6, 0, origin.tax_ids.ids)], 'invoice_line_tax_ids': [(6, 0, origin.tax_ids.ids)],
'account_analytic_id': line.folio_id.analytic_account_id.id, 'account_analytic_id': line.folio_id.analytic_account_id.id,
'analytic_tag_ids': [(6, 0, origin.analytic_tag_ids.ids)], 'analytic_tag_ids': [(6, 0, origin.analytic_tag_ids.ids)]
} }
if line.reservation_id: if line.reservation_id:
vals.update({'invoice_id': invoice_id, 'reservation_ids': [(6, 0, [origin.id])]}) vals.update({
'invoice_id': invoice_id,
'reservation_ids': [(6, 0, [origin.id])],
'reservation_line_ids': [(6, 0, line.reservation_line_ids.ids)]
})
elif line.service_id: elif line.service_id:
vals.update({'invoice_id': invoice_id, 'service_ids': [(6, 0, [origin.id])]}) vals.update({
'invoice_id': invoice_id,
'service_ids': [(6, 0, [origin.id])]
})
invoice_lines |= self.env['account.invoice.line'].create(vals) invoice_lines |= self.env['account.invoice.line'].create(vals)
return invoice_lines return invoice_lines

View File

@@ -49,6 +49,7 @@
nolabel="1"> nolabel="1">
<tree string="Lines" editable="bottom"> <tree string="Lines" editable="bottom">
<field name="product_id" invisible="1"/> <field name="product_id" invisible="1"/>
<field name="reservation_line_ids" />
<field name="reservation_id" /> <field name="reservation_id" />
<field name="service_id" /> <field name="service_id" />
<field name="description" /> <field name="description" />