package jamel.basicModel.firms; import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; import jamel.basicModel.banks.Account; import jamel.basicModel.banks.Bank; import jamel.basicModel.banks.Cheque; import jamel.basicModel.households.Shareholder; import jamel.basicModel.households.Worker; import jamel.util.Agent; import jamel.util.AgentDataset; import jamel.util.JamelObject; import jamel.util.NotUsedException; import jamel.util.Sector; /** * A basic firm. */ public class BasicFirm extends JamelObject implements Agent, Firm, Employer, Supplier { /** * To test the validity of job contracts. * * @return a predicate which returns <code>true</code> for not valid * contracts. */ final private static Predicate<LaborContract> isNotValid() { return contract -> !contract.isValid(); } /** * Returns the specified action. * * @param phaseName * the name of the action. * @return the specified action. */ static public Consumer<? super Agent> getAction(final String phaseName) { final Consumer<? super Agent> action; switch (phaseName) { case "opening": action = (agent) -> { ((BasicFirm) agent).open(); }; break; case "planProduction": action = (agent) -> { ((BasicFirm) agent).planProduction(); }; break; case "production": action = (agent) -> { ((BasicFirm) agent).production(); }; break; case "payWages": action = (agent) -> { ((BasicFirm) agent).payWages(); }; break; case "payDividends": action = (agent) -> { ((BasicFirm) agent).payDividends(); }; break; case "closure": action = (agent) -> { ((BasicFirm) agent).close(); }; break; default: throw new IllegalArgumentException(phaseName); } return action; } /** * The bank account of this firm. */ private final Account account; /** * The dataset. */ final private AgentDataset dataset; /** * The factory. */ final private Factory factory; /** * The id of this firm. */ final private int id; /** * The job offer of this firm. */ private final BasicJobOffer jobOffer; /** * A flag that indicates whether this firm is open or not. */ private boolean open = false; /** * The owners of the firm. */ private List<Shareholder> owners = new LinkedList<>(); /** * The list of the labor contracts. */ private final LinkedList<BasicLaborContract> payroll = new LinkedList<>(); /** * The parent sector. */ final private Sector sector; /** * The supply of this firm. */ private final BasicSupply supply; /** * The wage. Exogenously fixed. */ private final long wage = 700; /** * Creates a new firm. * * @param sector * the sector of this firm. * @param id * the id of this firm. */ public BasicFirm(final Sector sector, final int id) { super(sector.getSimulation()); this.sector = sector; this.id = id; this.dataset = new AgentDataset(this); this.account = ((Bank) this.getSimulation().getSector("Banks").select(1)[0]).openAccount(this); // THe string "Banks" should be a parameter. this.factory = new BasicFactory(this); this.jobOffer = new BasicJobOffer(this); this.supply = new BasicSupply(this); } /** * Closes the firm at the end of the period. */ private void close() { if (!this.open) { throw new RuntimeException("Already closed."); } this.supply.updateData(); this.dataset.put("countAgent", 1); this.dataset.put("inventoriesVolume", this.factory.getInventories().getVolume()); this.dataset.put("inventoriesValue", this.factory.getInventories().getValue()); this.dataset.put("money", this.account.getAmount()); this.dataset.put("assets", this.account.getAmount() + this.factory.getValue()); this.dataset.put("tangibleAssets", this.factory.getValue()); this.dataset.put("liabilities", this.account.getDebt()); this.open = false; } /** * Initializes the owners of this firm. */ private void initOwners() { final Agent[] selection = this.getSimulation().getSector("Shareholders").select(10); for (int i = 0; i < selection.length; i++) { if (selection[i] != null) { this.owners.add((Shareholder) selection[i]); } } } /** * Opens the firm at the beginning of the period. */ private void open() { if (this.open) { throw new RuntimeException("Already open."); } if (this.owners.isEmpty()) { initOwners(); } this.open = true; this.jobOffer.reset(); this.supply.reset(); } /** * The dividend payment phase. */ private void payDividends() { if (!this.open) { throw new RuntimeException("Closed."); } if (this.owners.isEmpty()) { throw new RuntimeException("No owners."); } final long cash = this.account.getAmount(); final long assets = cash + this.factory.getValue(); final long liabilities = this.account.getDebt(); final long capital = assets - liabilities; final long capitalTarget = (long) (assets * 0.5); final long capitalExcess = Math.max(capital - capitalTarget, 0); if (capitalExcess > this.owners.size()) { final long newDividend = Math.min(this.account.getAmount(), capitalExcess) / this.owners.size(); if (newDividend > 0) { for (final Shareholder shareholder : this.owners) { shareholder.acceptDividendCheque(this.account.issueCheque(shareholder, newDividend)); } } } } /** * The wage payment phase. */ private void payWages() { if (!this.open) { throw new RuntimeException("Closed."); } /* * Première passe : on calcule le wagebill. */ long wages = 0; for (BasicLaborContract contract : this.payroll) { wages += contract.getWage(); } /* * Besoin d'un financement ? */ if (wages > this.account.getAmount()) { this.account.borrow(wages - this.account.getAmount(), 12, false); } /* * Deuxième passe : on paie. */ for (BasicLaborContract contract : this.payroll) { contract.getWorker().acceptPayCheque(this.account.issueCheque(contract.getWorker(), contract.getWage())); } } /** * Phase of production planing. Decides how much to produce. */ private void planProduction() { if (!this.open) { throw new RuntimeException("Closed."); } // On commence par faire le ménage dans la liste des contrats de // travail, en retirant les contrats échus. this.payroll.removeIf(isNotValid()); // Déterminer le niveau souhaité de la production. final int workforceTarget = (int) (this.getRandom().nextFloat() * this.factory.getCapacity()); // TODO: le caractère aléatoire de l'objectif de production est // évidemment // provisoire. // Licencier ou embaucher. if (this.payroll.size() > workforceTarget) { do { // Last in first out. this.payroll.removeLast().breach(); } while (workforceTarget < this.payroll.size()); } else if (this.payroll.size() < workforceTarget) { this.jobOffer.setWage(this.wage); this.jobOffer.setVacancies(workforceTarget - this.payroll.size()); } this.dataset.put("jobOffers", this.jobOffer.getVacancies()); } /** * The production phase. */ private void production() { if (!this.open) { throw new RuntimeException("Closed."); } this.factory.production(this.payroll); if (this.factory.getInventories().getVolume() > 0) { final double markup = 1.2;// TODO markup should be a parameter this.supply.update(this.factory.getInventories().getVolume(), markup * this.factory.getInventories().getValue() / this.factory.getInventories().getVolume()); this.dataset.put("supplyVolume", this.factory.getInventories().getVolume()); this.dataset.put("supplyValue", markup * this.factory.getInventories().getValue()); this.dataset.put("supplyCost", this.factory.getInventories().getValue()); } } /** * Accepts the specified cheque. * * @param cheque * a cheque to be deposited on the firm account. */ void accept(Cheque cheque) { this.account.deposit(cheque); } /** * Returns the dataset of this firm. * * @return the dataset of this firm. */ AgentDataset getDataset() { return this.dataset; } /** * Returns a new job contract for the specified worker. * * @param worker * the applicant. * @return a new job contract. */ LaborContract getNewJobContract(Worker worker) { final int term = 12 + this.getRandom().nextInt(36); // TODO 12 and 36 should be parameters. final BasicLaborContract contract = new BasicLaborContract(getSimulation(), this, worker, this.jobOffer.getWage(), term); this.payroll.add(contract); return contract; } /** * Returns the specified volume of goods. * * @param volume * the volume of goods to be returned. * @return the specified volume of goods. */ Goods supply(int volume) { return this.factory.getInventories().take(volume); } @Override public Long getAssetTotalValue() { throw new NotUsedException(); } @Override public int getBorrowerStatus() { throw new NotUsedException(); } @Override public Double getData(String dataKey, String period) { return this.dataset.getData(dataKey); } @Override public JobOffer getJobOffer() { final JobOffer result; if (this.jobOffer.isEmpty()) { result = null; } else { result = this.jobOffer; } return result; } @Override public String getName() { return "firm_" + this.id; } @Override public Sector getSector() { return this.sector; } @Override public Supply getSupply() { final Supply result; if (!supply.isEmpty()) { result = this.supply; } else { result = null; } return result; } @Override public void goBankrupt() { throw new NotUsedException(); } @Override public boolean isBankrupted() { throw new NotUsedException(); } @Override public boolean isOpen() { return this.open; } @Override public boolean isSolvent() { throw new NotUsedException(); } }