def _cron_cleanup_obsolete(self, days=7): from_date = fields.Datetime.now().replace(hour=23, minute=59, second=59) limit_date = date_utils.subtract(from_date, days) records = self.search([("state", "=", "done"), ("done_on", "<=", limit_date)]) records.unlink() _logger.info("Cleanup obsolete images import. %d records found.", len(records))
def convertString2Datetime(self, strFull): """ Convert string to datetime :param strFull: EX: 2019-07-04T20:00:00.000-0400 :return: datetime (with timezone is UTC) """ strDateTime = strFull[:strFull.find(".")].replace("T", " ") dateTime = fields.Datetime.to_datetime(strDateTime) strTZ = strFull[-4:] if strFull.find("+") == -1: dateTime = date_utils.add(dateTime, hours=int(strTZ[:2]), minutes=int(strTZ[2:])) else: dateTime = date_utils.subtract(dateTime, hours=int(strTZ[:2]), minutes=int(strTZ[2:])) return dateTime
def to_UTCtime(timeStr): timeOdoo = fields.Datetime.to_datetime(timeStr[:timeStr.find(".")].replace( "T", " ")) #hh:mm utcDistance = timeStr[-4:] utcOp = timeStr[-5] if utcOp == "+": timeOdoo = date_utils.subtract(timeOdoo, hours=int(utcDistance[:2]), minutes=int(utcDistance[2:])) else: timeOdoo = date_utils.add(timeOdoo, hours=int(utcDistance[:2]), minutes=int(utcDistance[2:])) return timeOdoo
def _compute_json_replenishment_history(self): for replenishment_report in self: replenishment_history = [] today = fields.Datetime.now() first_month = subtract(today, month=2) date_from, dummy = get_month(first_month) dummy, date_to = get_month(today) domain = [('product_id', '=', replenishment_report.product_id.id), ('date', '>=', date_from), ('date', '<=', datetime.combine(date_to, time.max)), ('state', '=', 'done'), ('company_id', '=', replenishment_report.orderpoint_id.company_id.id)] quantity_by_month_out = self.env['stock.move'].read_group( AND([domain, [('location_dest_id.usage', '=', 'customer')]]), ['date', 'product_qty'], ['date:month']) quantity_by_month_returned = self.env['stock.move'].read_group( AND([domain, [('location_id.usage', '=', 'customer')]]), ['date', 'product_qty'], ['date:month']) quantity_by_month_returned = { g['date:month']: g['product_qty'] for g in quantity_by_month_returned } for group in quantity_by_month_out: month = group['date:month'] replenishment_history.append({ 'name': month, 'quantity': group['product_qty'] - quantity_by_month_returned.get(month, 0), 'uom_name': replenishment_report.product_id.uom_id.display_name, }) replenishment_report.json_replenishment_history = dumps({ 'template': 'stock.replenishmentHistory', 'replenishment_history': replenishment_history })
def _get_membership_interval(self, product, date): """Get the interval to evaluate as the theoretical membership period. :param product: Product that defines the membership :param date: date object for the requested date to determine the variable period :return: A tuple with 2 date objects with the beginning and the end of the period """ if product.membership_type == 'fixed': return super(AccountInvoiceLine, self)._get_membership_interval(product, date) if product.membership_interval_unit == 'days': raise exceptions.Warning( _("It's not possible to prorate daily periods.")) if product.membership_interval_unit == 'weeks': date_from = date_utils.start_of(date, 'week') elif product.membership_interval_unit == 'months': date_from = date_utils.start_of(date, 'month') elif product.membership_interval_unit == 'years': date_from = date_utils.start_of(date, 'year') date_to = date_utils.subtract(product._get_next_date(date), days=1) return date_from, date_to
def _online_sync_bank_statement(self, transactions, online_account): """ build a bank statement from a list of transaction and post messages is also post in the online_account of the journal. :param transactions: A list of transactions that will be created in the new bank statement. The format is : [{ 'id': online id, (unique ID for the transaction) 'date': transaction date, (The date of the transaction) 'name': transaction description, (The description) 'amount': transaction amount, (The amount of the transaction. Negative for debit, positive for credit) 'online_partner_information': optional field used to store information on the statement line under the online_partner_information field (typically information coming from plaid/yodlee). This is use to find partner for next statements }, ...] :param online_account: The online account for this statement Return: The number of imported transaction for the journal """ line_to_reconcile = self.env['account.bank.statement.line'] for journal in online_account.journal_ids: # Since the synchronization succeeded, set it as the bank_statements_source of the journal journal.sudo().write({'bank_statements_source': 'online_sync'}) if not transactions: continue transactions_identifiers = [ line['online_transaction_identifier'] for line in transactions ] existing_transactions_ids = self.env[ 'account.bank.statement.line'].search([ ('online_transaction_identifier', 'in', transactions_identifiers), ('journal_id', '=', journal.id) ]) existing_transactions = [ t.online_transaction_identifier for t in existing_transactions_ids ] transactions_partner_information = [] for transaction in transactions: transaction['date'] = fields.Date.from_string( transaction['date']) if transaction.get('online_partner_information'): transactions_partner_information.append( transaction['online_partner_information']) if transactions_partner_information: self._cr.execute( """ SELECT p.online_partner_information, p.id FROM res_partner p WHERE p.online_partner_information IN %s """, [tuple(transactions_partner_information)]) partner_id_per_information = dict(self._cr.fetchall()) else: partner_id_per_information = {} sorted_transactions = sorted(transactions, key=lambda l: l['date']) min_date = date_utils.start_of(sorted_transactions[0]['date'], 'month') if journal.bank_statement_creation_groupby == 'week': # key is not always the first of month weekday = min_date.weekday() min_date = date_utils.subtract(min_date, days=weekday) max_date = sorted_transactions[-1]['date'] total = sum([t['amount'] for t in sorted_transactions]) statements_in_range = self.search([('date', '>=', min_date), ('journal_id', '=', journal.id) ]) # For first synchronization, an opening bank statement is created to fill the missing bank statements all_statement = self.search_count([('journal_id', '=', journal.id) ]) digits_rounding_precision = journal.currency_id.rounding if journal.currency_id else journal.company_id.currency_id.rounding # If there are neither statement and the ending balance != 0, we create an opening bank statement if all_statement == 0 and not float_is_zero( online_account.balance - total, precision_rounding=digits_rounding_precision): opening_transaction = [(0, 0, { 'date': date_utils.subtract(min_date, days=1), 'payment_ref': _("Opening statement: first synchronization"), 'amount': online_account.balance - total, })] op_stmt = self.create({ 'date': date_utils.subtract(min_date, days=1), 'line_ids': opening_transaction, 'journal_id': journal.id, 'balance_end_real': online_account.balance - total, }) op_stmt.button_post() line_to_reconcile += op_stmt.mapped('line_ids') transactions_in_statements = [] statement_to_reset_to_draft = self.env['account.bank.statement'] transactions_to_create = {} for transaction in sorted_transactions: if transaction[ 'online_transaction_identifier'] in existing_transactions: continue # Do nothing if the transaction already exists line = transaction.copy() line['online_account_id'] = online_account.id if journal.bank_statement_creation_groupby == 'day': # key is full date key = transaction['date'] elif journal.bank_statement_creation_groupby == 'week': # key is first day of the week weekday = transaction['date'].weekday() key = date_utils.subtract(transaction['date'], days=weekday) elif journal.bank_statement_creation_groupby == 'bimonthly': if transaction['date'].day >= 15: # key is the 15 of that month key = transaction['date'].replace(day=15) else: # key if the first of the month key = date_utils.start_of(transaction['date'], 'month') # key is year-month-0 or year-month-1 elif journal.bank_statement_creation_groupby == 'month': # key is first of the month key = date_utils.start_of(transaction['date'], 'month') else: # key is last date of transactions fetched key = max_date # Find partner id if exists if line.get('online_partner_information'): partner_info = line['online_partner_information'] if partner_id_per_information.get(partner_info): line['partner_id'] = partner_id_per_information[ partner_info] # Decide if we have to update an existing statement or create a new one with this line stmt = statements_in_range.filtered(lambda x: x.date == key) if stmt: line['statement_id'] = stmt[0].id transactions_in_statements.append(line) statement_to_reset_to_draft += stmt[0] else: if not transactions_to_create.get(key): transactions_to_create[key] = [] transactions_to_create[key].append((0, 0, line)) # Create the lines that should be inside an existing bank statement and reset those stmt in draft if transactions_in_statements: for st in statement_to_reset_to_draft: if st.state != 'open': st.message_post(body=_( 'Statement has been reset to draft because some transactions from online synchronization were added to it.' )) statement_to_reset_to_draft.write({'state': 'open'}) line_to_reconcile += self.env[ 'account.bank.statement.line'].create( transactions_in_statements) # Recompute the balance_end_real of the first statement where we added line # because adding line don't trigger a recompute and balance_end_real is not updated. # We only trigger the recompute on the first element of the list as it is the one # the most in the past and this will trigger the recompute of all the statements # that are next. statement_to_reset_to_draft[0]._compute_ending_balance() # Create lines inside new bank statements created_stmts = self.env['account.bank.statement'] for date, lines in transactions_to_create.items(): # balance_start and balance_end_real will be computed automatically if journal.bank_statement_creation_groupby in ('bimonthly', 'week', 'month'): end_date = date if journal.bank_statement_creation_groupby == 'month': end_date = date_utils.end_of(date, 'month') elif journal.bank_statement_creation_groupby == 'week': end_date = date_utils.add(date, days=6) elif journal.bank_statement_creation_groupby == 'bimonthly': if end_date.day == 1: end_date = date.replace(day=14) else: end_date = date_utils.end_of(date, 'month') created_stmts += self.env['account.bank.statement'].create({ 'date': date, 'line_ids': lines, 'journal_id': journal.id, }) created_stmts.button_post() line_to_reconcile += created_stmts.mapped('line_ids') # write account balance on the last statement of the journal # That way if there are missing transactions, it will show in the last statement # and the day missing transactions are fetched or manually written, everything will be corrected last_bnk_stmt = self.search([('journal_id', '=', journal.id)], limit=1) if last_bnk_stmt and (created_stmts or transactions_in_statements): last_bnk_stmt.balance_end_real = online_account.balance # Set last sync date as the last transaction date journal.account_online_account_id.sudo().write( {'last_sync': max_date}) return line_to_reconcile
def get_production_schedule_view_state(self): """ Prepare and returns the fields used by the MPS client action. For each schedule returns the fields on the model. And prepare the cells for each period depending the manufacturing period set on the company. The forecast cells contains the following information: - forecast_qty: Demand forecast set by the user - date_start: First day of the current period - date_stop: Last day of the current period - replenish_qty: The quantity to replenish for the current period. It could be computed or set by the user. - replenish_qty_updated: The quantity to replenish has been set manually by the user. - starting_inventory_qty: During the first period, the quantity available. After, the safety stock from previous period. - incoming_qty: The incoming moves and RFQ for the specified product and warehouse during the current period. - outgoing_qty: The outgoing moves quantity. - indirect_demand_qty: On manufacturing a quantity to replenish could require a need for a component in another schedule. e.g. 2 product A in order to create 1 product B. If the replenish quantity for product B is 10, it will need 20 product A. - safety_stock_qty: starting_inventory_qty - forecast_qty - indirect_demand_qty + replenish_qty """ company_id = self.env.company date_range = company_id._get_date_range() # We need to get the schedule that impact the schedules in self. Since # the state is not saved, it needs to recompute the quantity to # replenish of finished products. It will modify the indirect # demand and replenish_qty of schedules in self. schedules_to_compute = self.env['mrp.production.schedule'].browse(self.get_impacted_schedule()) | self # Dependencies between schedules indirect_demand_trees = schedules_to_compute._get_indirect_demand_tree() # Get the schedules that do not depends from other in first position in # order to compute the schedule state only once. indirect_demand_order = schedules_to_compute._get_indirect_demand_order(indirect_demand_trees) indirect_demand_qty = defaultdict(float) incoming_qty, incoming_qty_done = self._get_incoming_qty(date_range) outgoing_qty, outgoing_qty_done = self._get_outgoing_qty(date_range) read_fields = [ 'forecast_target_qty', 'min_to_replenish_qty', 'max_to_replenish_qty', 'product_id', ] if self.env.user.has_group('stock.group_stock_multi_warehouses'): read_fields.append('warehouse_id') if self.env.user.has_group('uom.group_uom'): read_fields.append('product_uom_id') production_schedule_states = schedules_to_compute.read(read_fields) production_schedule_states_by_id = {mps['id']: mps for mps in production_schedule_states} for production_schedule in indirect_demand_order: # Bypass if the schedule is only used in order to compute indirect # demand. rounding = production_schedule.product_id.uom_id.rounding lead_time = production_schedule._get_lead_times() production_schedule_state = production_schedule_states_by_id[production_schedule['id']] if production_schedule in self: procurement_date = add(fields.Date.today(), days=lead_time) precision_digits = max(0, int(-(log10(production_schedule.product_uom_id.rounding)))) production_schedule_state['precision_digits'] = precision_digits production_schedule_state['forecast_ids'] = [] indirect_demand_ratio = production_schedule._get_indirect_demand_ratio(indirect_demand_trees, schedules_to_compute) starting_inventory_qty = production_schedule.product_id.qty_available if len(date_range): starting_inventory_qty -= incoming_qty_done.get((date_range[0], production_schedule.product_id, production_schedule.warehouse_id), 0.0) starting_inventory_qty += outgoing_qty_done.get((date_range[0], production_schedule.product_id, production_schedule.warehouse_id), 0.0) for date_start, date_stop in date_range: forecast_values = {} key = ((date_start, date_stop), production_schedule.product_id, production_schedule.warehouse_id) existing_forecasts = production_schedule.forecast_ids.filtered(lambda p: p.date >= date_start and p.date <= date_stop) if production_schedule in self: forecast_values['date_start'] = date_start forecast_values['date_stop'] = date_stop forecast_values['incoming_qty'] = float_round(incoming_qty.get(key, 0.0) + incoming_qty_done.get(key, 0.0), precision_rounding=rounding) forecast_values['outgoing_qty'] = float_round(outgoing_qty.get(key, 0.0) + outgoing_qty_done.get(key, 0.0), precision_rounding=rounding) forecast_values['indirect_demand_qty'] = float_round(indirect_demand_qty.get(key, 0.0), precision_rounding=rounding) replenish_qty_updated = False if existing_forecasts: forecast_values['forecast_qty'] = float_round(sum(existing_forecasts.mapped('forecast_qty')), precision_rounding=rounding) forecast_values['replenish_qty'] = float_round(sum(existing_forecasts.mapped('replenish_qty')), precision_rounding=rounding) # Check if the to replenish quantity has been manually set or # if it needs to be computed. replenish_qty_updated = any(existing_forecasts.mapped('replenish_qty_updated')) forecast_values['replenish_qty_updated'] = replenish_qty_updated else: forecast_values['forecast_qty'] = 0.0 if not replenish_qty_updated: replenish_qty = production_schedule._get_replenish_qty(starting_inventory_qty - forecast_values['forecast_qty'] - forecast_values['indirect_demand_qty']) forecast_values['replenish_qty'] = float_round(replenish_qty, precision_rounding=rounding) forecast_values['replenish_qty_updated'] = False forecast_values['starting_inventory_qty'] = float_round(starting_inventory_qty, precision_rounding=rounding) forecast_values['safety_stock_qty'] = float_round(starting_inventory_qty - forecast_values['forecast_qty'] - forecast_values['indirect_demand_qty'] + forecast_values['replenish_qty'], precision_rounding=rounding) if production_schedule in self: production_schedule_state['forecast_ids'].append(forecast_values) starting_inventory_qty = forecast_values['safety_stock_qty'] # Set the indirect demand qty for children schedules. for (mps, ratio) in indirect_demand_ratio: if not forecast_values['replenish_qty']: continue related_date = max(subtract(date_start, days=lead_time), fields.Date.today()) index = next(i for i, (dstart, dstop) in enumerate(date_range) if related_date <= dstart or (related_date >= dstart and related_date <= dstop)) related_key = (date_range[index], mps.product_id, mps.warehouse_id) indirect_demand_qty[related_key] += ratio * forecast_values['replenish_qty'] if production_schedule in self: # The state is computed after all because it needs the final # quantity to replenish. forecasts_state = production_schedule._get_forecasts_state(production_schedule_states_by_id, date_range, procurement_date) forecasts_state = forecasts_state[production_schedule.id] for index, forecast_state in enumerate(forecasts_state): production_schedule_state['forecast_ids'][index].update(forecast_state) # The purpose is to hide indirect demand row if the schedule do not # depends from another. has_indirect_demand = any(forecast['indirect_demand_qty'] != 0 for forecast in production_schedule_state['forecast_ids']) production_schedule_state['has_indirect_demand'] = has_indirect_demand return [p for p in production_schedule_states if p['id'] in self.ids]
# 2019-04-11 01:53:48 # 2019-04-12 01:53:48 # 2019-04-13 01:53:48 date_utils.add(today, days=5) # 2019-04-03 01:53:48 date_utils.add(today, weeks=2) # 2019-04-12 01:53:48 date_utils.add(today, months=1) # 2019-04-29 01:53:48 date_utils.add(today, years=1) # 2020-03-29 01:53:48 date_utils.add(today, days=2, months=6, years=1) # 2020-10-01 01:53:48 date_utils.subtract(today, days=5) # 2019-03-24 01:53:48 date_utils.subtract(today, weeks=2) # 2019-03-15 01:53:48 date_utils.subtract(today, months=1) # 2019-02-28 01:53:48 date_utils.subtract(today, years=1) # 2018-03-29 01:53:48 date_utils.subtract(today, days=2, months=6, years=1) # 2017-09-27 01:53:48 ''' json_default() Properly serializes date and datetime objects. @api.one @api.depends('payment_move_line_ids.amount_residual') def _get_payment_info_JSON(self):
def online_sync_bank_statement(self, transactions, journal, ending_balance): """ build a bank statement from a list of transaction and post messages is also post in the online_account of the journal. :param transactions: A list of transactions that will be created in the new bank statement. The format is : [{ 'id': online id, (unique ID for the transaction) 'date': transaction date, (The date of the transaction) 'name': transaction description, (The description) 'amount': transaction amount, (The amount of the transaction. Negative for debit, positive for credit) 'partner_id': optional field used to define the partner 'online_partner_vendor_name': optional field used to store information on the statement line under the online_partner_vendor_name field (typically information coming from plaid/yodlee). This is use to find partner for next statements 'online_partner_bank_account': optional field used to store information on the statement line under the online_partner_bank_account field (typically information coming from plaid/yodlee). This is use to find partner for next statements }, ...] :param journal: The journal (account.journal) of the new bank statement :param ending_balance: ending balance on the account Return: The number of imported transaction for the journal """ # Since the synchronization succeeded, set it as the bank_statements_source of the journal journal.sudo().write({'bank_statements_source': 'online_sync'}) if not len(transactions): return 0 transactions_identifiers = [ line['online_identifier'] for line in transactions ] existing_transactions_ids = self.env[ 'account.bank.statement.line'].search([ ('online_identifier', 'in', transactions_identifiers), ('journal_id', '=', journal.id) ]) existing_transactions = [ t.online_identifier for t in existing_transactions_ids ] sorted_transactions = sorted(transactions, key=lambda l: l['date']) min_date = date_utils.start_of(sorted_transactions[0]['date'], 'month') if journal.bank_statement_creation == 'week': # key is not always the first of month weekday = min_date.weekday() min_date = date_utils.subtract(min_date, days=weekday) max_date = sorted_transactions[-1]['date'] total = sum([t['amount'] for t in sorted_transactions]) statements_in_range = self.search([('date', '>=', min_date), ('journal_id', '=', journal.id)]) # For first synchronization, an opening bank statement is created to fill the missing bank statements all_statement = self.search_count([('journal_id', '=', journal.id)]) digits_rounding_precision = journal.currency_id.rounding if journal.currency_id else journal.company_id.currency_id.rounding if all_statement == 0 and not float_is_zero( ending_balance - total, precision_rounding=digits_rounding_precision): opening_transaction = [(0, 0, { 'date': date_utils.subtract(min_date, days=1), 'payment_ref': _("Opening statement: first synchronization"), 'amount': ending_balance - total, })] statement = self.create({ 'name': _('Opening statement'), 'date': date_utils.subtract(min_date, days=1), 'line_ids': opening_transaction, 'journal_id': journal.id, 'balance_end_real': ending_balance - total, }) statement.button_post() transactions_in_statements = [] statement_to_reset_to_draft = self.env['account.bank.statement'] transactions_to_create = {} number_added = 0 for transaction in sorted_transactions: if transaction['online_identifier'] in existing_transactions: continue line = transaction.copy() number_added += 1 if journal.bank_statement_creation == 'day': # key is full date key = transaction['date'] elif journal.bank_statement_creation == 'week': # key is first day of the week weekday = transaction['date'].weekday() key = date_utils.subtract(transaction['date'], days=weekday) elif journal.bank_statement_creation == 'bimonthly': if transaction['date'].day >= 15: # key is the 15 of that month key = transaction['date'].replace(day=15) else: # key if the first of the month key = date_utils.start_of(transaction['date'], 'month') # key is year-month-0 or year-month-1 elif journal.bank_statement_creation == 'month': # key is first of the month key = date_utils.start_of(transaction['date'], 'month') else: # key is last date of transactions fetched key = max_date # Decide if we have to update an existing statement or create a new one with this line stmt = statements_in_range.filtered(lambda x: x.date == key) if stmt and stmt[0].id: line['statement_id'] = stmt[0].id transactions_in_statements.append(line) statement_to_reset_to_draft += stmt[0] else: if not transactions_to_create.get(key): transactions_to_create[key] = [] transactions_to_create[key].append((0, 0, line)) # Create the lines that should be inside an existing bank statement and reset those stmt in draft if len(transactions_in_statements): for st in statement_to_reset_to_draft: if st.state == 'confirm': st.message_post(body=_( 'Statement has been reset to draft because some transactions from online synchronization were added to it.' )) st.state = 'posted' posted_statements = statement_to_reset_to_draft.filtered( lambda st: st.state == 'posted') posted_statements.state = 'open' statement_lines = self.env['account.bank.statement.line'].create( transactions_in_statements) posted_statements.state = 'posted' # Post only the newly created statement lines if the related statement is already posted. statement_lines.filtered(lambda line: line.statement_id.state == 'posted')\ .mapped('move_id')\ .with_context(skip_account_move_synchronization=True)\ ._post() # Recompute the balance_end_real of the first statement where we added line # because adding line don't trigger a recompute and balance_end_real is not updated. # We only trigger the recompute on the first element of the list as it is the one # the most in the past and this will trigger the recompute of all the statements # that are next. statement_to_reset_to_draft[0]._compute_ending_balance() # Create lines inside new bank statements st_vals_list = [] for date, lines in transactions_to_create.items(): # balance_start and balance_end_real will be computed automatically name = _('Online synchronization of %s') % (date, ) if journal.bank_statement_creation in ('bimonthly', 'week', 'month'): name = _('Online synchronization from %s to %s') end_date = date if journal.bank_statement_creation == 'month': end_date = date_utils.end_of(date, 'month') elif journal.bank_statement_creation == 'week': end_date = date_utils.add(date, days=6) elif journal.bank_statement_creation == 'bimonthly': if end_date.day == 1: end_date = date.replace(day=14) else: end_date = date_utils.end_of(date, 'month') name = name % (date, end_date) st_vals_list.append({ 'name': name, 'date': date, 'line_ids': lines, 'journal_id': journal.id }) statements = self.env['account.bank.statement'].create(st_vals_list) statements.button_post() # write account balance on the last statement of the journal # That way if there are missing transactions, it will show in the last statement # and the day missing transactions are fetched or manually written, everything will be corrected last_bnk_stmt = self.search([('journal_id', '=', journal.id)], limit=1) if last_bnk_stmt: last_bnk_stmt.balance_end_real = ending_balance if last_bnk_stmt.state == 'posted' and last_bnk_stmt.balance_end != last_bnk_stmt.balance_end_real: last_bnk_stmt.button_reopen() # Set last sync date as the last transaction date journal.account_online_journal_id.sudo().write({'last_sync': max_date}) return number_added
def get_production_schedule_view_state(self): company_id = self.env.company date_range = company_id._get_date_range() schedules_to_compute = self.env['mrp.production.schedule'].browse( self.get_impacted_schedule()) | self indirect_demand_trees = schedules_to_compute._get_indirect_demand_tree( ) indirect_demand_order = schedules_to_compute._get_indirect_demand_order( indirect_demand_trees) indirect_demand_qty = defaultdict(float) incoming_qty, incoming_qty_done = self._get_incoming_qty(date_range) outgoing_qty, outgoing_qty_done = self._get_outgoing_qty(date_range) read_fields = [ 'forecast_target_qty', 'min_to_replenish_qty', 'max_to_replenish_qty', 'product_id', ] if self.env.user.has_group('stock.group_stock_multi_warehouses'): read_fields.append('warehouse_id') if self.env.user.has_group('uom.group_uom'): read_fields.append('product_uom_id') production_schedule_states = schedules_to_compute.read(read_fields) production_schedule_states_by_id = { mps['id']: mps for mps in production_schedule_states } for production_schedule in indirect_demand_order: # Bypass if the schedule is only used in order to compute indirect # demand. ######################################## data = self.moq_of_product(production_schedule.product_tmpl_id) quantity_week = data['quantity_week'] moq = data['moq'] unidad_redondeo = data['unidad_redondeo'] ######################################## rounding = production_schedule.product_id.uom_id.rounding lead_time = production_schedule._get_lead_times() production_schedule_state = production_schedule_states_by_id[ production_schedule['id']] if production_schedule in self: procurement_date = add(fields.Date.today(), days=lead_time) precision_digits = max( 0, int(-(log10(production_schedule.product_uom_id.rounding)))) production_schedule_state[ 'precision_digits'] = precision_digits production_schedule_state['forecast_ids'] = [] indirect_demand_ratio = production_schedule._get_indirect_demand_ratio( indirect_demand_trees, schedules_to_compute) starting_inventory_qty = production_schedule.product_id.with_context( warehouse=production_schedule.warehouse_id.id).qty_available if len(date_range): starting_inventory_qty -= incoming_qty_done.get( (date_range[0], production_schedule.product_id, production_schedule.warehouse_id), 0.0) starting_inventory_qty += outgoing_qty_done.get( (date_range[0], production_schedule.product_id, production_schedule.warehouse_id), 0.0) pos = 0 for date_start, date_stop in date_range: forecast_values = {} key = ((date_start, date_stop), production_schedule.product_id, production_schedule.warehouse_id) existing_forecasts = production_schedule.forecast_ids.filtered( lambda p: p.date >= date_start and p.date <= date_stop) if production_schedule in self: forecast_values['date_start'] = date_start forecast_values['date_stop'] = date_stop forecast_values['incoming_qty'] = float_round( incoming_qty.get(key, 0.0) + incoming_qty_done.get(key, 0.0), precision_rounding=rounding) forecast_values['outgoing_qty'] = float_round( outgoing_qty.get(key, 0.0) + outgoing_qty_done.get(key, 0.0), precision_rounding=rounding) forecast_values['indirect_demand_qty'] = float_round( indirect_demand_qty.get(key, 0.0), precision_rounding=rounding) replenish_qty_updated = False if existing_forecasts: forecast_values['forecast_qty'] = float_round( sum(existing_forecasts.mapped('forecast_qty')), precision_rounding=rounding) forecast_values['replenish_qty'] = float_round( sum(existing_forecasts.mapped('replenish_qty')), precision_rounding=rounding) # Check if the to replenish quantity has been manually set or # if it needs to be computed. replenish_qty_updated = any( existing_forecasts.mapped('replenish_qty_updated')) forecast_values[ 'replenish_qty_updated'] = replenish_qty_updated else: forecast_values['forecast_qty'] = 0.0 # if not replenish_qty_updated: # replenish_qty = production_schedule._get_replenish_qty(starting_inventory_qty - forecast_values['forecast_qty'] - forecast_values['indirect_demand_qty']) # forecast_values['replenish_qty'] = float_round(replenish_qty, precision_rounding=rounding) # forecast_values['replenish_qty_updated'] = False ######################################## if replenish_qty_updated: if (pos >= quantity_week and forecast_values['replenish_qty'] > 0 and forecast_values['replenish_qty'] < moq): forecast_values['replenish_qty'] = moq else: if (pos < quantity_week and forecast_values.get('replenish_qty', 0) == 0): forecast_values['replenish_qty'] = 0 else: replenish_qty = production_schedule._get_replenish_qty( starting_inventory_qty - forecast_values['forecast_qty'] - forecast_values['indirect_demand_qty']) if unidad_redondeo > 0 and (replenish_qty % unidad_redondeo) > 0: resto = unidad_redondeo - (replenish_qty % unidad_redondeo) replenish_qty = replenish_qty + resto replenish_qty = float_round( replenish_qty, precision_rounding=rounding) if (replenish_qty > 0 and replenish_qty < moq): forecast_values['replenish_qty'] = moq else: forecast_values['replenish_qty'] = float_round( replenish_qty, precision_rounding=rounding) forecast_values['replenish_qty_updated'] = False pos = pos + 1 ######################################## forecast_values['starting_inventory_qty'] = float_round( starting_inventory_qty, precision_rounding=rounding) forecast_values['safety_stock_qty'] = float_round( starting_inventory_qty - forecast_values['forecast_qty'] - forecast_values['indirect_demand_qty'] + forecast_values['replenish_qty'], precision_rounding=rounding) if production_schedule in self: production_schedule_state['forecast_ids'].append( forecast_values) starting_inventory_qty = forecast_values['safety_stock_qty'] # Set the indirect demand qty for children schedules. for (mps, ratio) in indirect_demand_ratio: if not forecast_values['replenish_qty']: continue related_date = max(subtract(date_start, days=lead_time), fields.Date.today()) index = next( i for i, (dstart, dstop) in enumerate(date_range) if related_date <= dstart or ( related_date >= dstart and related_date <= dstop)) related_key = (date_range[index], mps.product_id, mps.warehouse_id) indirect_demand_qty[ related_key] += ratio * forecast_values['replenish_qty'] if production_schedule in self: # The state is computed after all because it needs the final # quantity to replenish. forecasts_state = production_schedule._get_forecasts_state( production_schedule_states_by_id, date_range, procurement_date) forecasts_state = forecasts_state[production_schedule.id] for index, forecast_state in enumerate(forecasts_state): ######################################## if (index < quantity_week): forecast_state['state'] = 'to_custom' ######################################## production_schedule_state['forecast_ids'][index].update( forecast_state) # The purpose is to hide indirect demand row if the schedule do not # depends from another. has_indirect_demand = any( forecast['indirect_demand_qty'] != 0 for forecast in production_schedule_state['forecast_ids']) production_schedule_state[ 'has_indirect_demand'] = has_indirect_demand return [p for p in production_schedule_states if p['id'] in self.ids]